0 00:00:00,000 --> 00:00:06,000 Translated by visionNoob, KNU https://github.com/insurgent92/CS231N_17_KOR_SUB 1 00:00:09,679 --> 00:00:13,891 12시가 지났으니 진행하도록 하겠습니다. 2 00:00:13,891 --> 00:00:17,822 이번 8강에서는 Deep learning software에 대해 알아볼 것입니다. 3 00:00:17,822 --> 00:00:21,283 매년 아주 많은 변화가 생기는 정말 재미있는 주제입니다. 4 00:00:21,283 --> 00:00:25,621 다만 매년 너무 많이 바뀌어서 매번 강의준비가 좀 빡세긴 합니다. 5 00:00:25,621 --> 00:00:30,024 우선 몇가지 공지사항을 전달하겠습니다. 6 00:00:30,024 --> 00:00:34,563 우선 지난 화요일에 프로젝트 기획서 제출기한이 마감되었습니다. 7 00:00:34,563 --> 00:00:42,766 다들 좋은 아이디어를 가지고 기한내에 잘 제출했길 바랍니다. 8 00:00:42,766 --> 00:00:50,217 현재 프로젝트 분야에 맞는 적절한 TA를 배치중에 있습니다. 9 00:00:50,217 --> 00:00:54,264 추후에 더 많은 정보가 공지될 것입니다. 10 00:00:54,264 --> 00:00:56,563 그리고 현재 과제1을 채점중에 있습니다. 11 00:00:56,563 --> 00:01:00,942 가능한 빨리 점수를 알려드리도록 하겠습니다. 12 00:01:00,942 --> 00:01:08,680 그리고 과제2의 제출기한도 얼마 남지 않았습니다. 다음 주 까지 라는 것을 꼭 명심하시기 바랍니다. 13 00:01:08,680 --> 00:01:16,231 과제2를 진행할 때 반드시 Google Cloud를 잘 종료시켜서 각자 크래딧을 아끼도록 하세요 14 00:01:16,231 --> 00:01:24,812 그리고 다시한번 말씀드리고 싶은 것은 과제2에서 GPU를 사용한만한 것은 마지막 문제뿐이 없다는 것입니다. 15 00:01:24,812 --> 00:01:32,250 나머지 문제들은 python과 numpy만 사용하기 때문에 GPU를 쓸 일이 없습니다. 16 00:01:32,250 --> 00:01:36,701 GPU는 정말 필요할 때만 사용하셔서 크래딧을 아껴 쓰시기 바랍니다. 17 00:01:36,701 --> 00:01:39,973 마지막으로 이제 곧 중간고사가 예정되어 있습니다. 18 00:01:39,973 --> 00:01:45,683 벌써 중간고사 기간이라는 것이 믿어지지 않지만 중간고사는 5월 9일 화요일 수업시간에 진행됩니다. 19 00:01:45,683 --> 00:01:47,901 중간고사는 이론중심으로 출제될 것입니다. 20 00:01:47,901 --> 00:01:57,071 지금까지 수업을 잘 이해하고 있는지를 확인하기 위한 몇가지 이론적인 문제들을 출제할 것입니다. 21 00:01:57,071 --> 00:02:02,506 몇 가지 예상문제를 올려놓도록 하겠습니다. 22 00:02:02,506 --> 00:02:03,695 질문 있나요? 23 00:02:03,695 --> 00:02:05,310 [학생이 질문] 24 00:02:05,310 --> 00:02:10,675 오픈북이냐고 질문하셨는데요 오픈북은 아닙니다. 25 00:02:10,675 --> 00:02:15,671 지금까지 항상 클로즈북으로 진행해 왔습니다. 26 00:02:15,671 --> 00:02:21,735 여러분이 수업에서 다룬 것들의 직관들을 얼마나 이해했는지를 확인하는 것 위주로 출제될 것입니다. 27 00:02:23,618 --> 00:02:27,577 자 그럼 지난시간의 내용을 간단하게 복습해 보도록 하겠습니다. 28 00:02:27,577 --> 00:02:29,737 지난 시간에 다양한 딥러닝 최적화 알고리즘을 배웠습니다. 29 00:02:29,737 --> 00:02:34,975 SGD, Momentum, Nesterov, RMSProp 그리고 Adam 에 대해서 배웠습니다. 30 00:02:34,975 --> 00:02:45,492 이 방법들을 모두 기본적인 SGD를 조금씩 변형시킨 방법이었죠 구현 자체는 간단하나 네트워크의 수렴속도는 더 빨랐습니다. 31 00:02:45,492 --> 00:02:48,529 그리고 regularization에 대해서도 배웠습니다. 특히나 Dropout에 관한 것이었죠 32 00:02:48,529 --> 00:02:56,975 Dropout은 forward pass에서 임의의 부분은 0으로 만들고 test time에서는 그 noise를 marginalize out 했습니다. 33 00:02:56,975 --> 00:03:02,805 그리고 딥러닝에서 사용하는 다양한 regularization들의 일반적인 패턴에 대해서는 배웠습니다. 34 00:03:02,805 --> 00:03:08,415 Train time에 noise를 추가하고, Test time에는 marginalize out 합니다. 35 00:03:08,415 --> 00:03:15,376 그리고 Transfer learning도 배웠습니다. Pre-train model을 다운받아서 fine-tune 하는 방법이죠 36 00:03:15,376 --> 00:03:21,314 Transfer learning을 이용하면 데이터셋이 많지 않더라도 딥러닝 문제를 다룰 수 있는 방법이었습니다. 37 00:03:22,781 --> 00:03:29,615 이번 시간에는 화제를 조금 돌려서 소프트웨어/하드웨어가 동작하는 방식에 대해서 조금 다배워보도록 하겠습니다. 38 00:03:29,615 --> 00:03:36,276 그리고 실제 여러분이 학습 시 사용하는 소프트웨어들을 조금 더 심도깊게 다뤄보도록 하겠습니다. 39 00:03:36,276 --> 00:03:43,967 정리하자면 CPU와 GPU에 대해 조금 배울 것이고, 요즘 사람들이 가장 많이 사용하는 립러닝 프레임워크들에 대해서도 배울 것입니다. 40 00:03:45,471 --> 00:03:52,961 자 우선 이런 얘기는 수도 없이 많이 들으셨을 것입니다. 컴퓨터에 CPU와 GPU가 있다는 것 말입니다. 41 00:03:52,961 --> 00:04:02,655 딥러닝에서 GPU를 사용하는데, 사실 지금까지 왜 GPU가 CPU가 좋은지 에 대해서 명확하게 언급하지 않고 넘어갔습니다. 42 00:04:02,655 --> 00:04:06,472 컴퓨터를 조립해본 적이 있나요? 손 한번 들어보시겠습니까 43 00:04:06,472 --> 00:04:10,965 한 1/3에서 절반정도는 계시는군요 44 00:04:10,965 --> 00:04:15,174 이 사진은 제 컴퓨터입니다. 제가 직접 조립했죠 45 00:04:15,174 --> 00:04:22,261 컴퓨터 내부에서 많은 부품들이 있습니다. 아마 여러분들은 각각이 무엇인지 아실 거라고 믿습니다. 46 00:04:22,261 --> 00:04:25,594 CPU는 Central Processing Unit이죠 47 00:04:25,594 --> 00:04:31,391 이 조그마한 칩은 쿨러 믿에 숨어있습니다. 48 00:04:31,391 --> 00:04:39,555 CPU는 엄청 작습니다. 공간을 크게 잡아먹지 않죠 49 00:04:39,555 --> 00:04:46,221 자 여기 GPU들을 보면 공간을 엄청 차지하고 있습니다. 괴물같은 녀석들입니다. 50 00:04:46,221 --> 00:04:50,296 GPU는 자기만의 쿨러가 있고 파워도 엄청 먹고 엄청 큽니다. 51 00:04:50,296 --> 00:04:59,139 GPU가 얼마나 많은 파워를 사용하는지 그리고 얼마나 큰지는 케이스에서 얼마나 많은 부분을 차지하는지만 봐도 알 수 있습니다. 52 00:04:59,139 --> 00:05:04,516 GPU란 것이 무엇이길래 딥러닝에서 이렇게 중요한 걸까요 53 00:05:04,516 --> 00:05:08,937 GPU는 graphics card 혹은 Graphics Processing Unit 이라고 합니다. 54 00:05:08,937 --> 00:05:16,166 사실 GPU는 computer graphics를 랜더링하기 위해서 만들어졌습니다. 게임같은 것들을 위해서죠 55 00:05:16,166 --> 00:05:23,247 자 다시한번 손을 들어보죠, 여기에 집에서 컴퓨터게임을 하시는 분들이 계십니까? 56 00:05:23,247 --> 00:05:25,693 절반정도 있군요 좋습니다. 57 00:05:25,693 --> 00:05:32,196 게임을 하시거나 컴퓨터를 직접 조립해 보신 분들이라면 다음과 같은 질문에 대한 나름대로의 의견을 가지고 계실 것입니다. 58 00:05:32,196 --> 00:05:34,095 [웃음] 59 00:05:34,095 --> 00:05:37,666 이는 Computer Science에서 가장 큰 논쟁 중 하나입니다. 60 00:05:37,666 --> 00:05:42,620 Intel vs AMD 라던가, 그래픽카드의 경우 NVIDIA vs AMD 같은 논쟁들이죠 61 00:05:42,620 --> 00:05:45,394 텍스트 에디터의 경우 Vim vs Emacs도 있겠군요 62 00:05:45,394 --> 00:05:51,945 대부분의 게이머들은 이 논쟁에 대해서 자기만의 의견을 가지고 있습니다. 63 00:05:51,945 --> 00:05:59,116 딥러닝의 경우는 대부분 한쪽만 선택합니다. NVIDIA죠 64 00:05:59,116 --> 00:06:05,117 만약 AMD 카드를 가지고있다면 아마 딥러닝에 사용하는데 문제가 많을 것입니다. 65 00:06:05,117 --> 00:06:08,812 지난 수년간 NVIDIA는 딥러닝에 많은 공을 들여왔습니다. 66 00:06:08,812 --> 00:06:11,997 NVIDIA의 전략은 세간의 주목을 받았습니다. 67 00:06:11,997 --> 00:06:19,354 Nvidia의 많은 엔지니어들은 딥러닝에 적합한 하드웨어를 만들기 위해서 많은 노력을 했습니다. 68 00:06:19,354 --> 00:06:27,718 그래서 딥러닝과 관련해서는 NVIDIA GPU가 거의 독점적으로 언급됩니다. 69 00:06:27,718 --> 00:06:35,268 아마 미래에는 또 다른 후발주자가 생겨날 지 모르겠지만 적어도 지금은 NVIDIA가 독점적입니다. 70 00:06:35,268 --> 00:06:41,705 자 그럼 CPU와 GPU의 차이는 무엇일까요? 제가 아주 간단하게 표로 한번 정리해 봤습니다. 71 00:06:41,705 --> 00:06:52,079 위에 두개는 Intel의 최신 상업용 CPU들이고 밑에 두개는 NVIDIA의 최신 상업용 GPU들입니다. 72 00:06:52,079 --> 00:06:55,975 여기에는 몇 가지 주요 트렌드가 있습니다. 73 00:06:55,975 --> 00:07:03,284 GPU와 CPU모두 임의의 명령어를 수행할 수 있는 범용 컴퓨팅 머신입니다. 74 00:07:03,284 --> 00:07:05,987 하지만 이 둘은 질적으로 아주 다릅니다. 75 00:07:05,987 --> 00:07:16,714 CPU의 경우 core의 수가 적습니다. 오늘날의 상업용 테스크탑 CPU의 경우에 코어가 4개에서 6개 조금 많으면 10개 정도입니다. 76 00:07:16,714 --> 00:07:24,893 그리고 hyperthreading 기술와 더불어 CPU는 8 ~ 20개의 스레드를 동시에 실행시킬 수 있습니다. 77 00:07:24,893 --> 00:07:29,700 따라서 CPU는 한번에 20가지의 일(스레드)을 할 수 있는 것입니다. 78 00:07:29,700 --> 00:07:34,527 이 숫자(20)가 그닥 커보이진 않습니다. 하지만 CPU의 멀티스레드는 아주 강력합니다. 79 00:07:34,527 --> 00:07:37,223 아주 많은 일을 할 수 있으면 엄청 빠릅니다. 80 00:07:37,223 --> 00:07:43,011 모든 CPU 명령어들은 정말 많은 일을 할 수 있죠 그리고 아주 독립적으로 수행합니다. 81 00:07:43,011 --> 00:07:51,909 하지만 GPU의 경우는 상황이 조금 다릅니다. 고성능의 상업 GPU의 경우 수천개의 코어가 있습니다. 82 00:07:51,909 --> 00:08:00,412 NVIDIA Titan XP와 같은 최상위 모델의 경우 3840개의 코어가 있습니다. 정말 엄청난 수 입니다. 83 00:08:02,223 --> 00:08:06,357 동일 가격의 CPU를 보면 코어가 10개 뿐이 없죠 84 00:08:06,357 --> 00:08:12,207 하지만 GPU의 단점을 보자면 각각의 코어가 더 느린 clock speed에서 동작한다는 점입니다. 85 00:08:12,207 --> 00:08:14,439 그리고 두번째는 그 코어들이 그렇게 많은 일을 할 수 없다는 것입니다. 86 00:08:14,439 --> 00:08:19,680 사실 CPU와 GPU코어는 1:1로 비교할 수 없습니다. 87 00:08:19,680 --> 00:08:22,510 GPU코어들은 독립적으로 동작하지 않습니다. 88 00:08:22,510 --> 00:08:29,297 코어마다 독립적인 테스크가 있는 것이 아니라 많은 코어들이 하나의 테스크를 병렬적으로 수행하는 것이죠 89 00:08:29,297 --> 00:08:32,405 따라서 코어의 수만 가지고 직접적으로 비교할 수는 없습니다. 90 00:08:32,405 --> 00:08:41,370 하지만 GPU의 코어의 수가 많다는 것은 어떤 테스크가 있을 때 이 일을 병렬로 수행하기 아주 적합하다는 것은 알 수 있습니다. 91 00:08:41,370 --> 00:08:44,742 하지만 그 테스크는 전부 같은 테스크여야 할 것입니다. 92 00:08:44,742 --> 00:08:49,387 그리고 또 한가지 말씀드릴 것은 메모리와 관련된 것입니다. 93 00:08:49,387 --> 00:08:58,523 CPU에도 캐시가 있습니다. 하지만 비교적 작습니다. CPU는 대부분의 메모리를 RAM에서 끌어다 씁니다. 94 00:08:58,523 --> 00:09:06,589 RAM은 일반적으로 8, 12, 16, 32 GB 바이트 정도이죠 95 00:09:06,589 --> 00:09:10,646 반면 GPU는 칩 안에 RAM이 내장되어 있습니다. 96 00:09:12,055 --> 00:09:22,675 실제 RAM와 GPU간의 통신은 상당한 보틀넥을 초래합니다. 그렇게 떄문에 GPU는 보통 칩에 RAM이 내장되어 있습니다. 97 00:09:23,955 --> 00:09:33,481 Titan XP의 경우 내장 메모리가 12GB정도 됩니다. 98 00:09:33,481 --> 00:09:41,790 GPU는 12GB의 메모리와 GPU 코어 사이의 캐싱을 하기 위한 일종의 다계층 캐싱 시스템을 가지고 있습니다. 99 00:09:41,790 --> 00:09:46,908 이는 CPU의 캐싱 계층구조와 아주 유사합니다. 100 00:09:47,985 --> 00:09:52,583 CPU는 범용처리에 적합합니다. CPU는 아주 다양한 일을 할 수 있죠 101 00:09:52,583 --> 00:09:57,089 그리고 GPU는 병렬처리에 더 특화되어 있습니다. 102 00:09:57,089 --> 00:10:04,106 GPU에서 정말 잘 동작하고 아주 적합한 알고리즘은 바로 행렬곱(Matrix multiplication) 연산입니다. 103 00:10:04,106 --> 00:10:14,348 왼쪽 행렬은 행이 엄청 많은 행렬입니다. 오른쪽은 열이 많죠 104 00:10:14,348 --> 00:10:25,009 오른쪽 행렬은 왼쪽 두 행렬의 내적입니다. 그리고 이 때의 내적 연산은 모두 서로 독립적입니다. 105 00:10:25,009 --> 00:10:33,653 오른쪽의 결과행렬을 살펴보면 각각의 원소가 전부 독립적입니다. 따라서 모두 병렬로 수행될 수 있습니다. 106 00:10:33,653 --> 00:10:38,289 그리고 각 원소들은 모두 같은 일을 수행합니다. 두개의 벡터를 내적하는 것이죠. 107 00:10:38,289 --> 00:10:44,177 다만 서로 입력 데이터만 조금씩 다를 뿐입니다. 108 00:10:44,177 --> 00:10:55,166 GPU는 결과 행렬의 각 요소들을 병렬로 계산할 수 있으며 이러한 특성 때문에 GPU는 엄청나게 빠릅니다. 109 00:10:55,166 --> 00:11:04,940 이런 연산들은 GPU가 정말 잘 하는 것들입니다. CPU였다면 각 원소를 하나씩만 계산할 것입니다. 110 00:11:06,337 --> 00:11:13,829 물론 CPU라고 그렇게 단순하게 동작하지는 않습니다. CPU는 여러개의 코어가 있고 Vectorized instructions이 존재합니다. 111 00:11:13,829 --> 00:11:19,568 그렇다고 해도 아주 massive한 병렬화 문제에 대해서는 GPU의 처리량이 압도적입니다. 112 00:11:19,568 --> 00:11:25,404 가령 행렬의 크기가 엄청 큰 경우가 될 수 있겠습니다. 그리고 비슷한 맥락으로 Convolution의 경우도 같습니다. 113 00:11:25,404 --> 00:11:36,359 convolution에는 입력(텐서)이 있고 가중치가 있죠 cov 출력은 마찬가지로 입력과 가중치간의 내적입니다. 114 00:11:36,359 --> 00:11:43,354 GPU의 경우라면 이 연산을 각 코어에 분배시켜서 아주 빠르게 연산할 수 있도록 해줍니다. 115 00:11:43,354 --> 00:11:49,510 이런 종류의 연산들이 일반적으로 CPU에 비해 GPU에서 연산 속도의 이점을 볼 수 있는 것들입니다. 116 00:11:51,695 --> 00:11:55,498 여러분들도 직접 GPU에서 실행되는 코드를 작성할 수 있습니다. 117 00:11:55,498 --> 00:12:03,614 NVIDIA에서 CUDA를 지원하는데 그 코드를 보면 c언어 스럽게 생겼습니다. 하지만 GPU에서 실행되는 코드입니다. 118 00:12:03,614 --> 00:12:05,484 하지만 CUDA코드를 작성하는 것은 상당히 까다롭습니다. 119 00:12:05,484 --> 00:12:12,002 GPU의 성능을 전부 짜낼 수 있는 코드를 작성하는 것은 상당히 힘든 일입니다. 120 00:12:12,002 --> 00:12:19,163 아주 세심하게 메모리구조를 관리해야 합니다. 가령 cache misses나 branch mispredictions 같은 것들을 전부 고려해야 하죠 121 00:12:19,163 --> 00:12:22,930 따라서 여러분 스스로 아주 효율적인 CUDA code를 작성하는 것은 상당히 어렵습니다. 122 00:12:22,930 --> 00:12:32,537 때문에 NVIDIA는 GPU에 고도로 최적화시킨 기본연산 라이브러리를 배포해 왔습니다. 123 00:12:32,537 --> 00:12:40,610 가령 cuBLAS는 다양한 행렬곱을 비롯한 연산들을 제공합니다. 이는 아주 고도로 최적화되어 있습니다. 124 00:12:40,610 --> 00:12:46,438 이는 GPU에서 아주 잘 동작하고 하드웨어 사용의 이론적 최대치까지 끌어올려 놓은 라이브러리입니다. 125 00:12:46,438 --> 00:12:54,499 유사하게 cuDNN이라는 라이브러리도 있습니다. 이는 convolution, forward/backward pass, batch norm, rnn 등 126 00:12:54,499 --> 00:12:57,454 등 딥러닝에 필요한 거의 모든 기본적인 연산들을 제공하고 있습니다. 127 00:12:57,454 --> 00:13:03,842 NVIDIA는 자사의 하드웨어에 아주 효율적으로 동작하는 라이브러리를 바이어리로 배포하고 있습니다. 128 00:13:03,842 --> 00:13:09,624 따라서 실제로는 딥러닝을 위해 CUDA 코드를 직접 작성하는 일은 없을 것입니다. 129 00:13:09,624 --> 00:13:14,173 이미 다른 사람들이 작성한 코드를 불러와서 쓰기만 하면 됩니다. 130 00:13:14,173 --> 00:13:19,573 이미 NVIDIA에서 극도로 최적화된 소스를 가져와서 쓰기만 하면 됩니다. 131 00:13:19,573 --> 00:13:23,693 그리고 또 하나의 언어가 있는데 OpenCL입니다. OpenCL이 조금 더 범용적입니다. 132 00:13:23,693 --> 00:13:29,185 NVIDIA GPU에서만 동작하는 것이 아니라 AMD 에서도 그리고 CPU에서도 동작합니다. 133 00:13:29,185 --> 00:13:43,938 하지만 OpenCL은 아직 딥러닝에 극도로 최적화된 연산이나 라이브러리가 개발되지는 않았습니다. 그래서 CUDA보다는 성능이 떨어집니다. 134 00:13:43,938 --> 00:13:51,839 미래에는 더 많은 open standard가 생기고 이종 플랫폼 간의 다양한 방법들이 생겨날지도 모르겠지만 135 00:13:51,839 --> 00:13:55,488 현재로썬 NVIDIA가 딥러닝의 선두주자입니다. 136 00:13:55,488 --> 00:14:01,686 GPU 프로그래밍을 직접 해보면 익힐 수 있는 다양한 리소스들이 있습니다. 아마 아주 흥미로울 것입니다. 137 00:14:01,686 --> 00:14:05,900 아주 massive한 병령처리 아키텍쳐를 다루는 것이기 때문에 코드를 작성하는 패러다임도 조금 다를 것입니다. 138 00:14:05,900 --> 00:14:08,023 하지만 이 이상은 우리 강의의 주제를 벗어나기 때문에 여기까지만 하겠습니다. 139 00:14:08,023 --> 00:14:12,263 다시한번 만씀드리지만 GPU로의 딥러닝을 위해서 굳이 스스로 CUDA code를 작성할 필요는 없습니다. 140 00:14:12,263 --> 00:14:16,600 사실 저의 경우도 연구 프로젝트를 진행하면서 CUDA code를 직접 짜본 적은 없습니다. 141 00:14:16,600 --> 00:14:22,219 하지만 여러분이 직접 코드를 짜진 않더라도 어떻게 동작하는지 잘 알아두는 것은 상당히 유욜할 수 있습니다. 142 00:14:23,488 --> 00:14:29,168 실제 CPU와 GPU의 성능을 한번 살펴보고 싶으시다면, 제가 작년 여름이 벤치마크 한 것이 있습니다. 143 00:14:29,168 --> 00:14:36,065 그때 당시 성능이 꽤 괜찮았던 Intel CPU와 성능이 가장 좋았던 NVIDIA GPU를 비교한 것입니다. 144 00:14:38,747 --> 00:14:48,954 더 자세한 내용은 제 Github에서 찾아볼 수 있습니다. VGG16/19와 다양한 ResNets을 이용했는데요 145 00:14:49,830 --> 00:14:57,114 그리고 그 결과 GPU에서 보통 65에서 75배의 speed up을 볼 수 있었습니다. 완전히 같은 연산량으로 비교한 것이죠 146 00:14:57,114 --> 00:15:00,984 GPU의 경우는 Pascal TitanX입니다. 147 00:15:00,984 --> 00:15:08,604 CPU의 경우 최상위 성능 까지는 아니였지만 Intel E5 Processor였습니다. 148 00:15:08,604 --> 00:15:15,550 하지만 여러분들이 이와 같은 딥러닝 벤치마크를 살펴볼 때 정말 조심해야 할 점이 있습니다. 149 00:15:15,550 --> 00:15:20,103 왜냐하면 비교군 간에 불공평한 비교를 하기 십상이기 떄문이죠 150 00:15:20,103 --> 00:15:26,339 따라서 여러분들이 벤치마크를 살펴볼 때 어떤 부분이 불공평한지를 주도면밀하게 살펴볼 필요가 있습니다. 151 00:15:26,339 --> 00:15:35,855 제가 이 벤치마크에 대해 말씀드려 보자면 이 결과는 사실 CPU에게는 조금 불리합니다. 152 00:15:35,855 --> 00:15:38,721 제가 CPU에서는 성능을 최대화 시킬 수 있을만큼 공을 들이기 않았기 때문입니다. 153 00:15:38,721 --> 00:15:42,483 저는 아마 BLAS 라이브러리가 CPU에서 더 잘 동작하도록 수정해 볼 수도 있었을 것입니다. 154 00:15:42,483 --> 00:15:44,540 그렇게 되면 이 표의 결과 보다는 조금 더 좋았을 것입니다. 155 00:15:44,540 --> 00:15:51,964 하지만 이또한 뛰어난 성능이긴 합니다. Torch를 설치하고 CPU와 GPU로 실행시켜본 것입니다. 156 00:15:51,964 --> 00:15:57,872 이 성능도 나쁘진 않지만 CPU의 성능을 극대화 시킨 것은 아닙니다. 157 00:15:57,872 --> 00:16:02,422 CPU의 성능을 높힐 수 있는 여지는 충분했습니다. 158 00:16:02,422 --> 00:16:15,543 그리고 또 하나의 벤치마크가 있는데 convolution 연산에 cuDNN을 사용한 것과 일반적인 CUDA를 사용한 것입니다. 159 00:16:15,543 --> 00:16:17,623 오픈소스 커뮤니티에 게제된 결과입니다. 160 00:16:17,623 --> 00:16:24,653 여기에서 볼 수 있는 것은 동일한 하드웨어와 동일한 딥러닝 프레임워크를 사용한 것이고 다른 점은 오직 161 00:16:24,653 --> 00:16:37,442 cuDNN/CUDA 입니다. CUDA은 조금 덜 최적화된 것이죠. cuDNN이 3X의 속도향상이 있습니다. 162 00:16:37,442 --> 00:16:45,202 일반적으로 여러분이 GPU 코드를 직접 작성해야 한다면 cuDNN을 반드시 사용해야 할 것입니다. 163 00:16:45,202 --> 00:16:51,602 cuDNN 라이브러리를 쓰고 안 쓰고의 차이가 거의 3배가 날테니 말입니다. 164 00:16:51,602 --> 00:17:02,882 그리고 실제로 GPU로 학습을 할 때 생기는 문제 중 하나는 바로 Model과 Model의 가중치는 전부 GPU RAM에 상주하고 있는 반면에 165 00:17:02,882 --> 00:17:07,243 실제 Train data(Big data)는 SSD와 같은 하드드라이브에 있다는 것입니다. 166 00:17:07,243 --> 00:17:13,204 때문에 Train time에 디스크에서 데이터를 읽어드리는 작업을 세심하게 신경쓰지 않으면 보틀넥이 발생할 수 있습니다. 167 00:17:14,321 --> 00:17:23,002 GPU는 forward/backward 가 아주 빠른 것은 사실이지만, 디스크 에서 데이터를 읽어드리는 것이 보틀넥이 되는 것입니다. 168 00:17:23,002 --> 00:17:25,699 이는 상당히 좋지 않은 상황이고 느려지게 될 것입니다. 169 00:17:25,700 --> 00:17:31,459 해결책 중 하나는 바로 데이터셋이 작은 경우에는 전체를 RAM에 올려 놓는 것입니다. 170 00:17:31,459 --> 00:17:36,479 데이터셋이 작지 않더라도, 서버에 RAM 용량이 크다면 가능할 수도 있을 것입니다. 171 00:17:36,479 --> 00:17:42,917 혹은 HDD대신에 SSD를 사용하는 방법이 있습니다. 데이터를 읽는 속도를 개선시킬 수 있겠죠 172 00:17:42,917 --> 00:17:52,152 또 한가지 방법은 바로 CPU의 다중스레드를 이용해서 데이터를 RAM에 미리 올려 놓는 것(pre-fetching) 입니다. 173 00:17:52,152 --> 00:17:57,724 그리고 buffer에서 GPU로 데이터를 전송시키게 되면 성능향상을 기대할 수 있을 것입니다. 174 00:17:57,724 --> 00:18:08,804 물론 이런 설정 자체가 조금 까다롭긴 하지만 GPU는 빠른데 데이터 전송 자체가 충분히 빠르지 못하면 보틀넥이 생길수 밖에 없습니다. 175 00:18:08,804 --> 00:18:11,657 이 부분은 항상 인지하고 있어야 합니다. 176 00:18:11,657 --> 00:18:17,432 지금까지는 딥러닝에 관련한 GPU와 CPU에 대한 간략한 소개였습니다. 177 00:18:17,432 --> 00:18:21,616 자 이제 조금 주제를 틀어서 소프트웨어와 관련된 이야기를 해볼까 합니다. 178 00:18:21,616 --> 00:18:25,006 실제로 사람들이 사용하는 딥러닝 프레임워크와 관련된 것입니다. 179 00:18:25,006 --> 00:18:28,819 다음으로 넘어가기에 앞서 혹시 CPU와 GPU에 관한 질문 있으십니까? 180 00:18:28,819 --> 00:18:30,519 네 질문있나요? 181 00:18:30,519 --> 00:18:34,686 [학생이 질문] 182 00:18:40,961 --> 00:18:45,854 질문은 바로 데이터읽기 에서의 병목을 해결하기 위해서 프로그래밍으로 우리가 할 수 있는 일이 무엇인지 입니다. 183 00:18:45,854 --> 00:18:50,833 소프트웨어적으로 할 수 있는 일은 CPU에서 미리 불러오는 것입니다.(pre-fetching) 184 00:18:50,833 --> 00:18:55,054 여러분은 다음과 같이 과정들이 순차적으로 진행되는 것을 원하지 않을 수 있습니다. 185 00:18:55,054 --> 00:18:58,791 디스크에서 데이터를 읽습니다. 그리고 미니배치의 데이터가 다 읽힐 때 까지 기다립니다. 186 00:18:58,791 --> 00:19:02,458 그리고 미니배치를 GPU에 전송합니다. 그리고 GPU에서 forward/backward를 수행하고 187 00:19:02,458 --> 00:19:05,442 그다음 또 다른 미니배치를 읽는 것이죠 이 단계를 전부 순차적으로 진행합니다. 188 00:19:06,714 --> 00:19:15,469 하지만 그러지 말고 CPU의 멀티스레딩을 통해 디스크에서 데이터를 불러오는 백그라운드 작업을 하고 있다면 189 00:19:15,469 --> 00:19:17,076 이 과정을 interleave하게 진행할 수 있을 것입니다. 190 00:19:17,076 --> 00:19:21,506 GPU이 계산을 수행하는 동안 CPU의 백그라운드 스레드는 디스크에서 데이터를 불러옵니다. 191 00:19:21,506 --> 00:19:28,534 그리고 CPU의 메인 스레드에서 synchronization를 관리하게 되면 이 모든 작업이 병렬로 수행될 수 있을 것입니다. 192 00:19:28,534 --> 00:19:38,016 그리고 고맙게도 여러분이 딥러닝 프레임워크를 사용한다면 이미 다 구현되어 있습니다. 일일이 구현하기 까다롭기 때문이죠 193 00:19:38,016 --> 00:19:41,738 딥러닝 프레임워크의 동향을 급변하고 있습니다. 194 00:19:41,738 --> 00:19:47,915 작년까지만 해도 Caffe, Torch, Tensorflow에 대해서만 언급했었습니다. 195 00:19:47,915 --> 00:20:00,232 1년 전까지만 해도 Tensorflow가 가장 최신의 프레임워크라서 사람들에게 널리 쓰이지 않았었습니다. 196 00:20:00,232 --> 00:20:06,310 하지만 지금은 아주 유명해져서 많은 사람들이 선택하는 메인 프레임워크가 되었습니다. 아주 큰 변화입니다. 197 00:20:07,342 --> 00:20:12,282 그리고 작년부터 정말 많은 프레임워크가 생겨나고 있습니다. 198 00:20:12,282 --> 00:20:18,052 특히나 Caffe2와 PyTorch가 생겼습니다.Facebook에서 밀고있는 프레임워크로 아주 흥미롭습니다. 199 00:20:18,052 --> 00:20:20,409 그 밖에도 정말 많은 프레임워크들이 있습니다. 200 00:20:20,409 --> 00:20:24,089 Baidu에는 Paddle이 있고 Microsoft는 CNTK이 있죠 201 00:20:24,089 --> 00:20:33,449 Amazon은 주로 MXNet을 사용하는데 이 밖에도 정말 많은 프레임워크가 있습니다. 다 써볼 시간도 부족하죠 202 00:20:33,449 --> 00:20:43,572 이 그림에서 주목할만한 재미있는 점은 바로 딥러닝 프레임워크의 초창기 세대는 학계(academia)에서 구축되었다는 것입니다. 203 00:20:43,572 --> 00:20:49,388 원래 Caffe은 Berkeley에서 Torch는 NYU에서 개발되었고 이후 Facebook과 공동연구를 시작한 것이죠 204 00:20:49,388 --> 00:20:52,077 Theana은 대부분 Montreal에서 개발했습니다. 205 00:20:52,077 --> 00:20:56,491 하지만 다음세대의 딥러닝 프레임워크들은 기업(industry)에서 태동했습니다. 206 00:20:56,491 --> 00:21:00,659 Caffe2와 PyTorch는 Facebook에서 나왔습니다. TensorFlow은 Google에서 나왔죠 207 00:21:00,659 --> 00:21:08,925 최근 몇 년간의 흥미로운 변화는 바로 academia에서 industry로의 이동이라고 할 수 있습니다. 208 00:21:08,925 --> 00:21:13,187 이제는 industry가 이러한 강력한 프레임워크를 제공하고 있습니다. 209 00:21:14,147 --> 00:21:24,850 오늘은 대부분 PyTorch와 TensorFlow에 대해서 말씀드릴 것입니다. 아마 여러분은 앞으로 이 두 프레임워크를 자주 다루게 될 것입니다. 210 00:21:24,850 --> 00:21:32,192 Caffe와 Caffe2도 간단히 설명해 드리겠습니다. 하지만 깊게 말씀드리진 않을 것입니다. 211 00:21:32,192 --> 00:21:36,705 우선 더 깊게 들어가기 전에 제가 어떤 것을 주로 사용했는지 미리 말씀드려야 할 것 같습니다. 212 00:21:36,705 --> 00:21:43,501 저 같은 경우에는 지난 몇 년간 Torch를 이용했습니다. 아주 많이 사용했고 참 좋아하는 녀석입니다. 213 00:21:43,501 --> 00:21:48,568 그리고 얼마전에 PyTorch로 갈아탔습니다. 214 00:21:48,568 --> 00:21:52,306 때문에 저 같은 경우 Tensorflow 같은 다른 플랫폼의 경험은 조금 부족합니다. 215 00:21:52,306 --> 00:21:58,382 그렇지만 저는 여러분들에게 최대한 잘 설명해 드리기 위해 노력 할 것입니다. 216 00:21:58,382 --> 00:22:06,807 자 그럼 지난 강의를 상기해보면 Computational graphs 에 대해서 수도없이 들었을 것입니다. 217 00:22:06,807 --> 00:22:13,176 여러분이 딥러닝과 관련된 작업을 한다면 computational graph를 빼고는 아무 것도 할 수 없죠 218 00:22:13,176 --> 00:22:18,778 여기 선형분류기의 예를 들자면 데이터 X가 있고 가중지 W가 있습니다. 그리고 이를 행렬곱으로 표현하죠 219 00:22:18,778 --> 00:22:22,832 그리고 hinge loss 같은 손실함수를 계산할 것입니다. 220 00:22:22,832 --> 00:22:28,909 regularization term도 있겠죠. 이런 모든 것들을 전부 그래프 구조 안에 표현할 수 있습니다. 221 00:22:28,909 --> 00:22:36,167 Neural net의 경우에 이 그래프구조는 상당히 복잡합니다. 다양한 레이어와 활성함수가 존재할 것입니다. 222 00:22:36,167 --> 00:22:39,687 그리고 아주 많은 가중치들이 그래프 전역에 퍼져 있을 것입니다. 223 00:22:39,687 --> 00:22:47,328 Neural turing machines의 그래프는 훨씬 더 복잡합니다. 너무 크고 복잡해서 그릴 수 조차 없습니다. 224 00:22:48,349 --> 00:22:58,727 따라서 우리가 굳이 코드를 손으로 작성하지 않고 딥러닝 프레임워크 를 이용하는데는 크게 세 가지 이유가 있습니다. 225 00:22:58,727 --> 00:23:08,610 우선 첫째는 딥러닝 프레임워크를 이용하게 되면 이처럼 엄청 복잡한 그래프를 우리가 직접 만들지 않아도 된다는 것입니다. 226 00:23:08,610 --> 00:23:13,956 두번째는, 딥러닝에서는 항상 그래디언트를 계산해야 하죠 227 00:23:14,812 --> 00:23:18,900 Loss를 계산하고 Loss에 부합하는 가중치의 그래디언트를 계산해야 합니다. 228 00:23:18,900 --> 00:23:26,115 그래디언트가 자동으로 계산되면 정말 좋을 것입니다. 이를 계산하는 코드를 직접 짜고싶은 사람은 없겠죠 229 00:23:26,115 --> 00:23:36,539 딥러닝 프레임워크를 사용하면 forward pass만 잘 구현해 놓으면 back propagation은 알아서 구성됩니다. 230 00:23:36,539 --> 00:23:42,000 그리고 마지막 세번째로는 여러분은 GPU을 효율적으로 사용하고 싶을 것입니다. 231 00:23:42,000 --> 00:23:48,389 하지만 cuBLAS, cuDNN, CUDA 그리고 memory등을 여러분이 직접 세심하게 다루고 싶지는 않을 것입니다. 232 00:23:48,389 --> 00:23:52,439 이런 모든 것들을 다루는 것은 정말 까다롭고 힘들 일이죠 233 00:23:52,439 --> 00:23:59,450 이러한 이유 때문에 보통 밑바닥 부터 구현하지 않고 프레임워크를 이용하는 것입니다. 234 00:23:59,450 --> 00:24:05,231 computational graph의 예를 한번 봅시다. 이런 식으로 엄청 간단한 예제를 만들어 볼 수 있겠죠 235 00:24:05,231 --> 00:24:13,071 입력으로 X,Y,Z가 있습니다. X와 Y를 곱하면 a가 됩니다. a와 Z를 더하면 b가 되죠 236 00:24:13,071 --> 00:24:18,630 그리고 b를 sum out 연산을 이용해 다 더하면 scaler 값 하나가 나옵니다. C 이죠 237 00:24:18,630 --> 00:24:31,631 여러분들은 아마 지금까지의 코드를 Numpy로 충분히 구현할 수 있다고 생각하실 지도 모릅니다. 238 00:24:31,631 --> 00:24:41,923 Numpy를 이용해서 random data를 입력으로 해서 적절하게 곱하고 더하고 하면 아주 쉽게 구현할 수 있겠죠 239 00:24:41,923 --> 00:24:48,355 하지만 X,Y,Z에 대한 그래디언트를 구하는 경우라면 어떨까요? 240 00:24:48,355 --> 00:24:52,725 여러분이 Numpy로 작성한 경우라면 backward도 여러분이 스스로 작성해야 합니다. 241 00:24:52,725 --> 00:25:02,859 아마도 여러분들이 과제에서 많이 해보셨겠지만 이 복잡한 수식을 구현하는 것은 아주 까다롭고 고통스러운 일입니다. 242 00:25:02,859 --> 00:25:05,675 그리고 Numpy의 또 하나의 문제점은 바로 GPU에서 돌아가지 않는다는 것입니다. 243 00:25:05,675 --> 00:25:14,920 Numpy는 CPU에서만 동작하죠. 그렇게 때문에 Numpy만 쓰는 사람들은 GPU의 참맛을 경험해 보지 못합니다. 244 00:25:14,920 --> 00:25:19,527 다시 말씀드리자면 그때 그때 마다 그래디언트를 여러분 스스로 계산하는 것은 상당히 힘든 일입니다. 245 00:25:19,527 --> 00:25:29,047 때문에 딥러닝 프레임워크의 목표는 바로 여러분이 forward pass 코드를 Numpy 스럽게 작성을 해 놓으면 246 00:25:29,047 --> 00:25:33,069 GPU에서도 동작하고 그래디언트도 알아서 계산해 주는 것입니다. 247 00:25:33,069 --> 00:25:36,397 이는 프레임워크가 존재하는 가장 큰 목적입니다. 248 00:25:36,397 --> 00:25:44,314 Tensorflow의 예를 한번 볼까요? 두 코드의 computational graph는 동일합니다. forward pass를 한번 봅시다. 249 00:25:44,314 --> 00:25:52,687 Tensorflow 의 코드를 보면 Numpy의 forward pass 코드와 아주 유사합니다. 곱셈/덧셈 연산을 한번 보시죠 250 00:25:52,687 --> 00:25:57,623 하지만 Tensorflow에는 여러분을 위해 그래디언트를 계산해 주는 마법의 코드가 있습니다. 251 00:25:57,623 --> 00:26:02,235 직접 backward pass를 작성 할 필요가 없는 것입니다. 아주 유용한 기능입니다. 252 00:26:02,235 --> 00:26:08,926 그리고 Tensorflow가 좋은 점 중 하나는 명령어 한 줄이면 CPU/GPU에서 동작하도록 전환 시킬 수 있다는 것입니다. 253 00:26:08,926 --> 00:26:16,668 forward pass에서 이런 식으로 선언을 해주면 난 CPU로 돌리고 싶어! 라고 하는것 과 같죠 254 00:26:16,668 --> 00:26:24,866 그리고 단지 'C'를 'G'로 바꾸기만 하면 이 코드는 GPU에서 동작하게 됩니다. 255 00:26:24,866 --> 00:26:31,388 코드 몇 줄만 추가해 주면 두 가지 문제를 해결할 수 있는 것입니다. 우선 GPU를 사용할 수 있게 해주는 것이고 256 00:26:31,388 --> 00:26:35,685 모든 그레디언트를 계산할 수도 있습니다. 아주 큰 장점입니다. 257 00:26:35,685 --> 00:26:38,459 PyTorch도 거의 비슷합니다. 258 00:26:38,459 --> 00:26:42,509 PyTorch의 예로 다시한번 살펴보자면 여기에 번수를 선언하고 259 00:26:42,509 --> 00:26:49,262 forward pass를 진행합니다. 여기에서도 Numpy에서의 코드와 아주 유사하다는 것을 알 수 있습니다. 260 00:26:49,262 --> 00:26:56,251 PyTorch에서도 한줄이면 그레디언트를 계산할 수 있습니다. 261 00:26:56,251 --> 00:27:06,781 PyTorch에서 GPU를 사용하려면 CUDA data type으로 선언하기만 하면 됩니다. 그러면 GPU에서 연산이 수행되죠 262 00:27:06,781 --> 00:27:13,878 지금까지 세가지 예제를 살펴보았습니다. Numpy, Tensorflow, PyTorch 입니다. 263 00:27:13,878 --> 00:27:20,564 TensorFlow와 PyTorch모두 Numpy 스럽게 생겼습니다. 264 00:27:20,564 --> 00:27:24,349 Numpy가 사용하기 쉽기 때문에 Numpy처럼 생긴 것은 정말 좋은 특성입니다. 265 00:27:24,349 --> 00:27:29,192 Numpy스럽게 생겼을 뿐만 아니라 그레디언트도 알아서 계산해주고 GPU에서도 자동으로 돌아갑니다. 266 00:27:30,186 --> 00:27:37,502 간략한 소개가 끝났으니 이제 TensorFlow에 대해서 좀더 심도깊게 알아보도록 하겠습니다. 267 00:27:37,502 --> 00:27:50,662 앞으로는 이 한가지 예시를 가지고 계속 말씀드릴 것입니다. 두개의 fc layer + ReLU를 학습시키는 네트워크 입니다. 268 00:27:50,662 --> 00:27:55,289 그리고 손실함수로는 L2 Euclidean 를 사용합니다. 269 00:27:55,289 --> 00:28:08,966 이 네트워크의 성능을 기대하긴 어렵겠지만 딥 러닝을 프레임워크를 설명하는데는 아주 재격입니다. 슬라이드 크기에도 딱 맞죠 270 00:28:08,966 --> 00:28:15,900 그리고 이 코드는 numpy와 tensorflow를 이미 import한 것입니다. 271 00:28:15,900 --> 00:28:21,163 Tensorflow의 경우에는 코드가 크게 두가지 스테이지로 나뉩니다. 272 00:28:21,163 --> 00:28:28,363 상단에 빨간색 박스를 보시죠. 우선은 computational graph 를 정의하는 코드를 작성할 것입니다. 273 00:28:28,363 --> 00:28:32,360 그래프를 정의하고 나면 그래프를 실행시켜야겠죠 274 00:28:32,360 --> 00:28:36,851 우선 연산을 수행하려면 데이터를 넣어줘야 합니다. 275 00:28:36,851 --> 00:28:40,961 Tensorflow를 사용할 때 아주 일반적인 순서가 되겠습니다. 276 00:28:40,961 --> 00:28:46,615 우선 그래프를 구성하는 코드를 한번 작성해 놓으면 여러분은 이 그래프를 언제든지 실행시킬 수 있습니다. 277 00:28:48,099 --> 00:28:52,763 그래프를 구성하는 부분을 좀 더 자세히 살펴봅시다. 278 00:28:52,763 --> 00:29:00,709 우선 맨 처음에 X,Y,w1,w2를 정의합니다. 이들은 모두 tf.placeholder 객체입니다. 279 00:29:01,637 --> 00:29:05,193 이들은 그래프의 입력노드가 됩니다. 280 00:29:05,193 --> 00:29:15,379 computational graph의 입구 역할을 하는 것이죠. 가령 데이터를 넣어주고자 하면 이쪽으로 넣어주면 됩니다. 281 00:29:15,379 --> 00:29:21,944 하지만 이 부분에서 실제적인 메모리할당이 일어나진 않습니다. 단지 그래프만 구성하는 것입니다. 282 00:29:23,272 --> 00:29:28,665 앞서 선언한 입력 변수를 이용해 봅시다. 283 00:29:28,665 --> 00:29:37,135 이 변수들을 기반으로 다양한 Tensorflow 연산을 수행합니다. 여러분이 만들고 싶은 연산을 구성할 수 있는 것이죠 284 00:29:37,135 --> 00:29:46,109 이 예제의 경우 X와 w1의 행렬곱 연산을 수행합니다. 그리고 tf.maximum을 이용해 ReLU를 구현할 수 있습니다. 285 00:29:46,109 --> 00:29:49,240 그리고 행렬곱 연산을 한번 더 수행해서 그래프의 최종 출력값을 계산할 수 있습니다. 286 00:29:49,240 --> 00:29:58,175 그리고 예측값과 정답값 Y의 유클리디안 거리를 계산하기 위해서도 Tensor operation 을 사용합니다. 287 00:29:58,175 --> 00:30:05,824 한가지 주목할 점은 이 코드는 현재 아무런 연산도 수행하지 않는다는 점입니다. 아직까지는 계산할 데이터가 존재하지 않습니다. 288 00:30:05,824 --> 00:30:15,001 지금은 단지 그래프 구조만 만들어 놓아서 실제 데이터가 들어왔을때 어떻게 연산을 진행해야 하는지만 구성합니다. 289 00:30:15,001 --> 00:30:18,648 그래프를 구성하는 것 외에 아무 일도일어나지 않습니다. 290 00:30:18,648 --> 00:30:33,135 그리고 이 빨간색 라인을 통해 로스를 계산하고 w1과 w2의 그레디언트를 계산해주는 마법적일 일을 할 수 있습니다. 291 00:30:33,135 --> 00:30:37,981 이를 통해 여러분이 과제에서 했던 backporp 코드를 직접 짜는 수고를 덜 수 있습니다. 292 00:30:37,981 --> 00:30:40,439 이 때도 사실은 실제 계산이 이루어지지는 않습니다. 293 00:30:40,439 --> 00:30:51,108 여기에서도 단지 그래프에 그레디언트를 계산해 주는 추가적인 연산을 더해준 것과 같습니다. 294 00:30:51,108 --> 00:31:01,421 자 지금까지는 computational graph를 구성했습니다. Loss와 그레디언트를 계산하기 위한 커다란 그래프를 만든 것이죠 295 00:31:01,421 --> 00:31:06,843 자 이제 Tensorflow session으로 들어가 봅시다. 이를 통해 실제 그래프를 실행시키고 데이터를 넣어줄 수 있습니다. 296 00:31:06,843 --> 00:31:13,859 session에 진입하려면 그래프에 들어갈 실제 값을 만들어 줘야 합니다. 297 00:31:13,859 --> 00:31:19,459 대부분의 경우 TensorFlow는 Numpy arrays 이용할 수 있습니다. 298 00:31:19,459 --> 00:31:30,226 이 경우에도 Numpy를 이용해서 x,w1,w2,y의 값을 할당해 줬습니다. 299 00:31:30,226 --> 00:31:32,743 그리고 바로 이 부분이 실제로 그래프를 실행시키는 부분입니다. 300 00:31:32,743 --> 00:31:38,120 session.run 을 이용해서 그래프의 일부를 실행시킬 수 있습니다. 301 00:31:38,120 --> 00:31:43,899 첫 번째 인자가 loss라는 것은, 우리가 그래프의 출력으로 어떤 부분을 원하는 지를 말해주는 부분입니다. 302 00:31:43,899 --> 00:31:50,950 예제에서는 우리가 현재 Loss와 grad1과 grad2를 계산하기를 원한다고 말해주고 있는 것입니다. 303 00:31:50,950 --> 00:31:57,140 그리고 feed_dict를 통해 실제 값을 전달해 주는 것이죠 304 00:31:57,140 --> 00:32:06,541 이 한 줄만 실행시키면 그래프가 실행되고 loss와 grad1과 grad2가 계산됩니다. 305 00:32:06,541 --> 00:32:12,003 그리고 출력 값들도 Numpy array입니다. 306 00:32:12,003 --> 00:32:19,859 output이란 변수를 나눠보게 되면 loss와 gradient가 Numpy array의 형태로 반환됨을 알 수 있습니다. 307 00:32:19,859 --> 00:32:23,697 이 값을 가지고 여러분이 원하는 작업을 하시면 됩니다. 308 00:32:23,697 --> 00:32:29,599 이 경우에는 그래프에서 단 한 번의 forward/backward pass 를 수행한 경우 입니다. 309 00:32:29,599 --> 00:32:33,167 네트워크를 학습시키기 위해서는 단 몇 줄이면 충분합니다. 310 00:32:33,167 --> 00:32:45,511 그래프를 여러번 실행시키기 위해서는 for loop를 사용하면 됩니다. 반복적으로 session.run을 호출해 loss와 grad를 계산합니다. 311 00:32:45,511 --> 00:32:52,291 이 예제에서는 가중치를 업데이트하기 위해 그레디언트를 계산해서 "수동으로(manual)" Gradient diescent를 하고 있습니다. 312 00:32:52,291 --> 00:33:00,749 이 코드의 loss를 계산해 보면 loss가 아주 잘 내려가며 이는 네트워크의 학습이 이루어지는 것입니다. 아주 잘 동작하는 것이죠 313 00:33:00,749 --> 00:33:06,113 이 예제는 TensorFlow에서 네트워크가 어떤 식으로 학습이 되는지를 아주 명확하게 알려줍니다. 314 00:33:06,113 --> 00:33:08,046 하지만 여기에는 문제가 하나 있습니다. 315 00:33:08,046 --> 00:33:15,086 forward pass에서 그래프가 실행될 때마다 가중치를 넣어줘야 합니다. 316 00:33:15,086 --> 00:33:18,835 현재 Numpy로 된 가중치를 가지고 있고 이 값을 그래프에 넣어줘야 하는 것이죠 317 00:33:18,835 --> 00:33:26,339 그래프가 한번 실행되고 나면 그레디언트를 반환해 줬습니다. 그레디언트는 가중치와 동일한 크기의 행렬이었죠 318 00:33:26,339 --> 00:33:32,665 이것이 의미하는 바는 여러분이 그래프를 실행할 때 마다 우선 Numpy array로 된 가중치행렬을 Tensorflow로 복사합니다. 319 00:33:32,665 --> 00:33:36,419 그리고 gradient를 계산해서는 gradient행렬을 Tensorflow에서 Numpy array로 반환해 주는 것이죠 320 00:33:36,419 --> 00:33:39,849 만약 코드가 CPU에서 실행이 되고 있다면 큰 문제가 되지는 않을 것입니다. 321 00:33:39,849 --> 00:33:47,235 하지만 지난번에 GPU의 보틀넥에 대해서 말씀드린 적이 있었죠. CPU/GPU 메모리산의 데이터교환을 상당히 비용이 큽니다. 322 00:33:47,235 --> 00:33:59,256 때문에 여러분의 네트워크가 엄청 크고 가중치가 엄청 많다면 이런 GPU/CPU간의 데이터 교환은 이는 엄청 느리고 비용이 클 것입니다. 323 00:33:59,256 --> 00:34:01,689 이것은 안좋은 일입니다. 바꿀 필요가 있습니다. 324 00:34:01,689 --> 00:34:06,027 TensorFlow에는 이에 대한 해결책이 있습니다. 325 00:34:06,027 --> 00:34:17,969 굳이 매번 placeholder를 써서 가중치를 넣어 줄 필요는 없습니다. 대신에 단지 variables로 선언하는 것이죠 326 00:34:17,969 --> 00:34:27,346 variable은 computational graph안에 서식하는 변수입니다. 그래프가 실행될 때 마다 지속적으로 그래프 안에 상주합니다. 327 00:34:27,347 --> 00:34:33,094 따라서 w1와 w2를 placeholder 대신에 variables로 선언해 줍니다. 328 00:34:33,094 --> 00:34:39,219 하지만 이제는 그들이 그래프 안에 살기 때문에 우리는 Tensorflow에게 어떻게 초기화시킬 것인지를 알려줘야 합니다. 329 00:34:39,219 --> 00:34:44,606 왜냐하면 기존에 그래프 밖에 있을때는 그래프에 넣어주기 전에 Numpy를 이용해서 초기화 시켜주면 그만이었습니다. 330 00:34:44,606 --> 00:34:50,569 하지만 지금은 그래프 안에 있기 때문에 그 변수들을 초기화시킬 권한은 TensorFlow에 있는 것입니다. 331 00:34:50,569 --> 00:34:53,149 따라서 우리는 tr.randomnormal 이 필요합니다. 332 00:34:53,149 --> 00:35:00,627 사실 저 명령어 자체가 변수들을 초기화시켜 주는 것은 아니고 Tensorflow에게 어떻게 이 값들이 초기화되야 하는지는 알려줍니다. 333 00:35:00,627 --> 00:35:03,215 이 부분에서 다소 헷갈릴 수 있습니다. 334 00:35:04,869 --> 00:35:11,862 자 그리고 이전 예제에서는 computational graph의 외부에서 가중치를 업데이트했습니다. 335 00:35:11,862 --> 00:35:17,219 이전 예제에서는 그레디언트를 계산하고 Numpy array로 가중치를 업데이트 했죠 336 00:35:17,219 --> 00:35:20,264 그리고 계산된 가중치를 다음 스텝에 다시 넣어줬습니다. 337 00:35:20,264 --> 00:35:29,402 하지만 가중치를 그래프 안에서 업데이트 하기 위해서는 그 연산 자체가 그래프에 포함되어야 합니다. 338 00:35:29,402 --> 00:35:37,020 이를 위해 assign 함수를 이용합니다. 이를 통해 변수가 그래프 내에서 업데이트가 일어나도록 하는 것입니다. 339 00:35:37,020 --> 00:35:41,487 이 업데이트된 값들은 항상 그래프 내부에 존재하게 됩니다. 340 00:35:41,487 --> 00:35:45,976 자 이제 실제 학습을 진행하기에 앞서 (tf.global_variables_initializer() 를 통해) 341 00:35:45,976 --> 00:35:53,825 그래프 내부의 변수들을 초기화시켜 주는 명령어가 필요합니다. 342 00:35:53,825 --> 00:35:58,574 초기화가 끝나면 이제 그래프를 돌릴 차례입니다. 343 00:35:58,574 --> 00:36:05,091 이 코드를 보시면 이제는 데이터와 레이블만 넣어주면 되고 가중치는 그래프 내부에 항상 상주하게 됩니다. 344 00:36:05,091 --> 00:36:09,517 여기에서는 Tensorflow에게 loss를 계산해 달라고 하는 것이죠 345 00:36:09,517 --> 00:36:13,001 여러분은 이 코드로 네트워크를 학습시킬 수 있다고 생각할 수 있습니다. 346 00:36:13,001 --> 00:36:19,964 하지만 사실 이 코드에는 버그가 있습니다. 이 코드를 돌려서 Loss를 그려보면 전혀 학습이 안됩니다. 347 00:36:19,964 --> 00:36:23,401 도대체 왜 이런 것일까요? 348 00:36:23,401 --> 00:36:29,957 assign 코드도 작성했고 그래프를 돌려서 Loss와 그레디언트도 계산을 잘 했는데 Loss가 변하질 않습니다. 왜 그럴까요? 349 00:36:29,957 --> 00:36:31,460 아시는 분 계신가요? 350 00:36:31,460 --> 00:36:34,595 [학생이 대답] 351 00:36:34,595 --> 00:36:44,979 대답은 가중치가 매번 초기화되기 때문이라는 것인데요 그럴듯한 답변이지만 사실 그 문제는 아닙니다. 352 00:36:44,979 --> 00:36:48,057 [학생이 대답] 353 00:36:48,057 --> 00:36:56,318 대답은 바로 우리가 Tensorflow에게 w1과 w2를 업데이트하라고 명시적으로 말해 줘야 한다는 것입니다. 354 00:36:56,318 --> 00:36:58,835 그래서 우리는이 큰 계산 그래프 데이터를 구축했습니다. 355 00:36:58,835 --> 00:37:01,699 구조를 메모리에 저장하고 실행을 호출 할 때, 356 00:37:01,699 --> 00:37:04,894 우리는 TensorFlow에게 우리가 손실을 계산하기를 원한다고 말했습니다. 357 00:37:04,894 --> 00:37:09,155 그리고 그래프 내부의 dependence를 고려 해 보면 358 00:37:09,155 --> 00:37:13,715 Loss를 계산하기 위해서 굳이 업데이트를 할 필요는 없다는 것을 알게 될 것입니다. 359 00:37:13,715 --> 00:37:21,496 Tensorflow는 아주 스마트하기 때문에 output 에 필요한 연산만 수행합니다. 360 00:37:21,496 --> 00:37:26,656 이는 Tensorflow의 장점이기도 합니다. 필요한 부분만 실행하기 때문이지요 361 00:37:26,656 --> 00:37:32,739 하지만 간혹 이 특성이 우리를 헷갈리게 하고 예상하지 못했던 결과를 초래할 수도 있습니다. 362 00:37:32,739 --> 00:37:39,141 이 경우에는 우리가 Tensorflow에게 업데이트를 수행하라고 명시적으로 말해줘야 합니다. 363 00:37:39,141 --> 00:37:49,531 해결법 중 하나는 new_w1과 new_w2 를 출력으로 추가하면 됩니다. 364 00:37:49,531 --> 00:37:57,366 하지만 new_w1과 new_w2의 사이즈가 큰 tensor라면은 상황이 안좋아집니다. 365 00:37:58,891 --> 00:38:05,138 Tensorflow에게 출력을 요청하는 것은 매 반복마다 CPU/GPU간의 데이터 전송이 요구되기 때문입니다. 366 00:38:05,138 --> 00:38:07,316 이는 좋지 않죠 367 00:38:07,316 --> 00:38:11,742 이 대신에 할 수 있는 트릭이 하나 있습니다. 더미 노드(dummy node)하나를 그래프에 추가하는 것이죠 368 00:38:11,742 --> 00:38:20,307 이 fake data dependencies를 이용하면 new_w1과 new_w2를 업데이트 할 수 있습니다. 369 00:38:20,307 --> 00:38:25,803 그리고 그래프를 실행시키면 loss와 더미노드를 계산하게 됩니다. 370 00:38:25,803 --> 00:38:38,468 이 더미노드는 아무 것도 반환하지 않지만 dependence를 만들었기 때문에 이를 통해 가중치가 업데이트 될 수 있는 것입니다. 371 00:38:38,468 --> 00:38:39,551 질문있나요? 372 00:38:40,788 --> 00:38:44,955 [학생이 질문] 373 00:38:45,854 --> 00:38:51,370 질문은 바로 X와 Y도 그래프에 넣어주지 않은 이유는 무엇인지 입니다. 이들은 여전히 Numpy array로 되어있죠 374 00:38:51,370 --> 00:38:57,151 이 예제에서는 우리가 매 반복시마다 동일한 X와 Y를 재사용 하고 있습니다. 375 00:38:57,151 --> 00:39:10,122 이 경우에는 넣어줘도 되겠죠 하지만 좀 더 현실적인 경우에는 X와 Y는 미니배치입니다. 매번 바뀌기 때문에 매번 넣어줘야 합니다. 376 00:39:10,122 --> 00:39:14,330 이 예제에서는 그래프 안에 넣어주는 것이 가능할 지 모르겠으나 실제로 대부분의 경우에는 그렇지 않습니다. 377 00:39:14,330 --> 00:39:17,913 대부분의 경우에는 X,Y가 그래프 안에 있으면 안좋습니다. 378 00:39:19,388 --> 00:39:21,290 다른 질문 있나요? 379 00:39:21,290 --> 00:39:25,457 [학생이 질문] 380 00:39:37,046 --> 00:39:44,305 현재 우리는 TensorFlow에게 loss와 update를 출력하길 원한다고 말했습니다. 381 00:39:44,305 --> 00:39:51,801 사실 Updates는 어떤 값이 아니죠. 따라서 Updates는 None을 반환합니다. 382 00:39:51,801 --> 00:39:57,416 하지만 dependence로 인해 updates를 통해 assign operations을 수행할 수 있는 것이죠 383 00:39:57,416 --> 00:40:02,358 그리고 assign operations은 그래프 내부에 있기 때문에 전부 GPU 메모리 내에 상주하는 녀석들입니다. 384 00:40:02,358 --> 00:40:10,190 덕분에 GPU 내부에서 업데이트를 진행하면서 이 결과를 다시 CPU로 가져오지 않아도 되도록 해 주는 것입니다. 385 00:40:11,723 --> 00:40:15,112 [학생이 질문] 386 00:40:15,112 --> 00:40:18,195 질문은 tf.group은 왜 none을 반환하는지 입니다. 387 00:40:18,195 --> 00:40:25,923 여기에는 TensorFlow의 트릭이 숨어있습니다. tf.group자체는 어떤 복잡한 TensorFlow 객체를 반환합니다. 388 00:40:25,923 --> 00:40:32,658 그래프를 구성하기 위해 필요한 일종의 TensorFlow 노드 객체를 반환하는 것이죠 389 00:40:32,658 --> 00:40:43,333 그리고 session.run를 통해서 그래프를 실행하면 그때 바로 none을 반환하는 것입니다. 390 00:40:43,333 --> 00:40:45,482 따라서 TensorFlow로 작업 할 때마다 391 00:40:45,482 --> 00:40:53,487 그래프를 구성하는 동안 반환되는 값들은 Tensorflow 노드와 관련된 어떤 객체들이고 392 00:40:53,487 --> 00:40:55,466 실제 그래프를 돌려야 실제 원하는 값들이 나오는 것입니다. 393 00:40:55,466 --> 00:40:59,967 여기에서는 update를 돌리면 출력으로 none이 나오는 것입니다. 이해가 좀 되셨나요? 394 00:40:59,967 --> 00:41:04,134 [학생이 질문] 395 00:41:18,796 --> 00:41:22,334 질문은 바로 loss는 왜 값이 있고 updata는 none인지 입니다. 396 00:41:22,334 --> 00:41:24,068 이는 바로 update가 동작하는 방식에 있습니다. 397 00:41:24,068 --> 00:41:30,176 loss의 경우에는 우리가 계산하는 실제 값이 있습니다. 398 00:41:30,176 --> 00:41:35,753 반면 update는 특수한 데이터 타입입니다. 실행을 해도 값을 반환하지 않습니다. none을 반환하죠 399 00:41:35,753 --> 00:41:38,703 이는 일종의 TensorFlow의 마법이라고 할 수 있습니다. 400 00:41:38,703 --> 00:41:40,602 아직 헷갈리면 수업이 끝나고 조금 더 말해보죠 401 00:41:40,602 --> 00:41:42,678 [학생이 질문] 402 00:41:42,678 --> 00:41:46,186 네 맞습니다. update의 마법은 group이라는 함수에서 비롯되는 것입니다. 403 00:41:46,186 --> 00:41:52,492 assign operations를 위해서 tf.group을 이용하는 것은 사실 정상적인 방법은 아닌 것 같아 보입니다. 404 00:41:52,492 --> 00:42:00,004 별로 좋은 방법은 아닌 것 같습니다. 하지만 고맙게도 Tensorflow는 이런 일들을 해주는 편리한 명령어들을 제공해 줍니다. 405 00:42:00,004 --> 00:42:01,706 이는 바로 optimizer입니다. 406 00:42:01,706 --> 00:42:06,047 여기 보시면 tf.train.GradientDescentOptimizer 라는 것을 사용하고 있습니다. 407 00:42:06,047 --> 00:42:08,458 learning rate도 정해줍니다. 408 00:42:08,458 --> 00:42:12,784 여기에 Adam이나 RMSprop같은 다양한 oprimization 알고리즘이 있는 것입니다. 409 00:42:12,784 --> 00:42:16,284 자 이제 optimizer.minimize(loss) 를 호출합니다. 410 00:42:17,311 --> 00:42:21,204 이는 아주 마법같은 일을 합니다. 411 00:42:21,204 --> 00:42:30,586 이는 w1과 w2가 학습 가능하다는 것을 인식합니다. optimizer.minimize의 내부를 살펴보면 412 00:42:30,586 --> 00:42:35,184 그래프에 w1와 w2의 그레디언트를 계산하는 노드도 추가하고 413 00:42:35,184 --> 00:42:42,219 그리고 w1와 w2의 update operation도 추가합니다. 그리고 assigns을 위한 grouping operation도 있죠 414 00:42:42,219 --> 00:42:44,206 이 같은 많은 일들이 내부적으로 수행됩니다. 415 00:42:44,206 --> 00:42:53,518 이 함수를 들여다보면 tf.group도 들어가 있는 등 이전에 봤던 예제와 유사한 일을 하는 것입니다. 416 00:42:53,518 --> 00:43:00,004 자 이제 반복문을 살펴보면 매 반복마다 loss와 update를 계산해 달라고 하고 있죠 417 00:43:00,004 --> 00:43:07,450 매번 그래프에게 update를 계산하라고 할 때 마다 update가 일어납니다. 418 00:43:07,450 --> 00:43:08,593 질문 있나요? 419 00:43:08,593 --> 00:43:10,959 [학생이 질문] 420 00:43:10,959 --> 00:43:14,249 질문은 tf.GlobalVariablesInitializer 가 무엇인지 입니다. 421 00:43:14,249 --> 00:43:20,502 이는 w1과 w2를 초기화시켜 줍니다. 이 변수들을 그래프 내부에 존재하기 때문이죠 422 00:43:20,502 --> 00:43:37,733 앞서 tf.variable을 만들때 tf.randomnormal이 들어갔죠 tf.GlobalVariablesInitializer이 실제 초기화를 합니다. 423 00:43:37,733 --> 00:43:40,794 [학생이 질문] 424 00:43:40,794 --> 00:43:42,271 잘 못들었습니다? 425 00:43:42,271 --> 00:43:45,233 [학생이 질문] 426 00:43:45,233 --> 00:43:51,385 placeholder는 그래프 밖에서 데이터를 넣어주는 변수이고 variable은 그래프 내부에 있는 변수입니다. 427 00:43:51,385 --> 00:44:00,384 저도 자세한 것은 모르지만 코드를 살펴보거나 관련 문서를 참고하시기 바랍니다. 428 00:44:00,384 --> 00:44:06,130 자 지금까지는 TensorFlow로 네트워크를 학습시키는 전체 예제를 한번 살펴보았고 429 00:44:06,130 --> 00:44:09,328 이를 더 편리하게 하는 몇 가지 요소들도 살펴보았습니다. 430 00:44:09,328 --> 00:44:16,954 이전 까지의 예제에서는 loss를 계산하는 연산을 우리가 직접 만들었습니다. 431 00:44:16,954 --> 00:44:20,739 하지만 TensorFlow에서 제공하는 기본적인 tensor operations을 이용하는 방법도 있습니다. 432 00:44:20,739 --> 00:44:26,734 Tensorflow는 일반적인 Neural network 모델에 들어가는 편리한 함수들을 제공합니다. 433 00:44:26,734 --> 00:44:30,040 tf.losses.mean_squared_error를 사용할 수 있습니다. 434 00:44:30,040 --> 00:44:36,273 이 tensor operations를 사용하면 L2 loss를 직접 구현하지 않아도 됩니다. 435 00:44:36,273 --> 00:44:46,667 그리고 귀찮은 것이 하나 더 있습니다. 입력과 가중치를 정의하고 행렬 곱연산으로 둘을 묶는 그런 일들입니다. 436 00:44:46,667 --> 00:44:54,291 이 예제에서는 bias도 넣지 않았습니다. bias를 넣을라 치면 bias를 또 초기화시켜 줘야 하고 437 00:44:54,291 --> 00:44:58,494 적절한 shape으로 맞춰도 줘야 하고 또 출력 값에 맞춰서 broadcasting이 필요할 수도 있을 것입니다. 438 00:44:58,494 --> 00:45:01,966 bias 하나를 추가하는데도 엄청나게 많은 코드가 필요한 것이죠 439 00:45:01,966 --> 00:45:03,664 이를 일일이 다 작성하는 것은 불편합니다. 440 00:45:03,664 --> 00:45:09,653 여러분이 딥러닝에 들어가는 기본적인 레이어들 가령 convolutions이나 batch norm을 구현하려 치면 441 00:45:09,653 --> 00:45:17,403 입/출력도 선언해 줘야하고 이를 모두 묶어서 computational graph를 만들어줘야 하는데 442 00:45:17,403 --> 00:45:22,954 이의 가중치를 초기화시켜주고 shape를 잘 맞춰주는 일은 정말로 성가신 일입니다. 443 00:45:22,954 --> 00:45:30,615 이에 TensorFlow를 warpping한 higher level libraries이 존재합니다. 444 00:45:30,615 --> 00:45:35,965 TensorFlow에서 제공해주는 것중 하나는 tf.layers입니다. 445 00:45:35,965 --> 00:45:44,060 이 예제를 보시면 X와 Y만 placeholders로 선언해 줍니다. 446 00:45:44,060 --> 00:45:53,036 그리고 밑에 줄을 보시면 h = tf.layers라고 선언합니다. 그리고 inputs = x, units = H 를 넣어줍니다. 447 00:45:53,036 --> 00:45:55,171 이 또한 아주 마법스러운 것입니다. 448 00:45:55,171 --> 00:46:07,411 내부적으로 w1과 b2을 variables로 만들어주고 그래프 내부에 적절한 shape으로 선언해 줍니다. 다만 우리에게는 보이지 않습니다. 449 00:46:07,411 --> 00:46:12,931 그리고 xavier initialize 객체를 사용하여 어떻게 초기화시킬 것인지를 말해줍니다. 450 00:46:12,931 --> 00:46:17,200 기존에는 tf.randomnormal을 사용해서 일일이 초기화시켜 줬었죠 451 00:46:17,200 --> 00:46:22,266 하지만 지금은 이 모든 것들을 알아서 해줍니다. 그리고 h가 출력으로 나옵니다. 452 00:46:22,266 --> 00:46:27,515 다음 레이어를 보면 이전 레이어 출력인 h를 받아서 똑같은 일을 해줍니다. 453 00:46:28,487 --> 00:46:36,910 그리고 여기 activation=tf.nn.relu 를 볼 수 있는데 이를 통해 레이어에 relu를 추가해 줄 수 있습니다. 454 00:46:36,910 --> 00:46:41,370 이처럼 이들은 모두 우리를 대신에서 대부분의 아키텍쳐와 관련된 세부사항들을 다뤄줍니다. 455 00:46:41,370 --> 00:46:42,784 질문 있나요? 456 00:46:42,784 --> 00:46:46,446 [학생이 질문] 457 00:46:46,446 --> 00:46:51,168 질문은 xavier initializer이 특정 분포를 기본값으로 정해 놓고 있는지 입니다. 458 00:46:51,168 --> 00:46:55,850 아마도 기본값이 있긴 할테지만 정확히는 모르겠네요 관련 문서를 한번 찾아보시기 바랍니다. 459 00:46:55,850 --> 00:46:58,010 관련 문서를 찾아보는 것은 좋은 전략이 될 수 있습니다. 460 00:46:58,010 --> 00:47:04,111 가중치 초기화를 더 잘해서 모델이 이전보다 더 빨리 수렴할 수도 있다면 말이죠 461 00:47:04,111 --> 00:47:11,911 우리는 단지 tf.layers만 두번 호출했을 뿐인데 모델을 구출할 수 있었습니다. 세부사항을 다 기술할 필요가 없죠 462 00:47:11,911 --> 00:47:14,273 이는 정말 편리합니다. 463 00:47:14,273 --> 00:47:18,682 하지만 우리가 쓸 수 있는 라이브러리가 tf.contrib.layer만 있는 것은 아닙니다. 464 00:47:18,682 --> 00:47:23,349 TensorFlow를 기반으로 한 아주 다양한 higher level libraries가 있습니다. 465 00:47:23,349 --> 00:47:26,841 다양한 라이브러리가 존재하는 이유는 computational graph가 상대적으로 low level이기 때문입니다. 466 00:47:26,841 --> 00:47:30,315 computational graph는 상대적으로 low level입니다. 467 00:47:30,315 --> 00:47:36,426 반면 우리가 Neural network를 다룰때면 레이어와 가중치에 대한 개념을 다룹니다. 468 00:47:36,426 --> 00:47:41,866 따라서 자연스럽게 computational graph 에 대한 조금은 더 higher level인 추상화 모델을 생각해 볼 수 있을 것입니다. 469 00:47:41,866 --> 00:47:48,503 다양한 라이브러리와 패키지들은 여러분이 조금 더 높은 추상화레벨에서 작업할 수 있도록 돕는 것입니다. 470 00:47:48,503 --> 00:47:52,460 아주 유명한 패키지 하나를 더 소개시켜 드리겠습니다 Keras입니다. 471 00:47:52,460 --> 00:48:02,806 Keras은 아주 훌륭한 APUI로 TensorFlow를 backend로 해서 computational graph를 알아서 만들어줍니다. 472 00:48:02,806 --> 00:48:07,704 Keras는 Theano backend도 지원합니다. 473 00:48:07,704 --> 00:48:10,958 이 예제에서 레이어의 시퀀스로 모델을 구성하는 것을 보실 수 있습니다. 474 00:48:10,958 --> 00:48:17,910 그리고 optimizer 객체를 만들고 model.compile을 하면 그래프가 알아서 만들어지는 마법이 이루어지는 것이죠 475 00:48:17,910 --> 00:48:22,797 그리고 model.fit을 하게되면 전체 학습과정이 알아서 진행됩니다. 476 00:48:22,797 --> 00:48:28,523 저는 더 자세한 내용은 잘 모르지만 Keras는 아주 유명하고 여러분이 TensorFlow관련된 일을 한다면 써볼만합니다. 477 00:48:29,797 --> 00:48:31,270 질문있나요? 478 00:48:31,270 --> 00:48:35,437 [학생이 질문] 479 00:48:41,717 --> 00:48:45,525 질문은 바로 여기에는 왜 CPU/GPU에 대한 명시가 없댜는 것입니다. 480 00:48:45,525 --> 00:48:48,409 제가 깔끔한 코드를 위해서 생략한 것입니다. 481 00:48:48,409 --> 00:48:54,607 하지만 keras tutorial 을 살펴보시면 아시겠지만 CPU/GPU 전환은 아주 쉽습니다. global flag을 사용하거나 482 00:48:54,607 --> 00:49:01,635 특정 데이터 타입을 사용하거나 with문을 이용할 수도 있는데 어쩄든 한 줄만 추가하면 될것입니다. 아주 쉽죠 483 00:49:01,635 --> 00:49:06,149 다만 현재 상황에 따라 선언하는 방식은 상이할 수 있습니다. 484 00:49:06,149 --> 00:49:14,186 지금 보시는 것은 실제로 여러분들이 앞으로 접하게 될 higher level TensorFlow wrappers들입니다. 485 00:49:14,186 --> 00:49:21,276 심지어 Google 사람들 조차도 어떤 것이 더 좋은지 잘 모르는 것 같습니다. 486 00:49:22,230 --> 00:49:26,829 Keras와 TFLearn은 third party libraries 입니다. Google이 아닌 다른 사람들이 만든 라이브러리들입니다. 487 00:49:26,829 --> 00:49:32,563 그리고 여기 tf.layers, TF-Slim, tf.contrib.learn 이 있습니다. 488 00:49:32,563 --> 00:49:39,727 TensorFlow와 함께 제공되는 라이브러리 입니다. 세가지 모두 다 조금씩 다른 방식으로 wrapping되어 있죠 489 00:49:39,727 --> 00:49:46,291 Google에서 만들었지만 같이 제공하지는 않은 라이브러리도 있습니다. Pretty Tensor라는 것이고 비슷한 맥락입니다. 490 00:49:46,291 --> 00:49:48,599 하지만 DeepMind에게는 이 모든것들이 만족스럽지 않았나 봅니다. 491 00:49:48,599 --> 00:49:54,530 몇 주 전에 Sonnet이라는 자체 Tensorflow wapper를 만들었죠 492 00:49:54,530 --> 00:50:00,715 더이상 나가면 여러분들이 혼란스러운 것 같아 그만하겠습니다. 결론은 여러분들에게 다양한 선택지가 있다는 것입니다. 493 00:50:00,715 --> 00:50:07,423 그것 들이 상호간에 호환이 잘 안될 수는 있겠지만, 어찌됐든 여러분에게든 여러 옵션이 있는 것이니 좋은 것이죠 494 00:50:07,423 --> 00:50:09,123 TensorFlow는 pretrained models를 제공합니다. 495 00:50:09,123 --> 00:50:11,112 여기 주소에 TF-Slim와 Keras에서의 예제가 있습니다. 496 00:50:11,112 --> 00:50:15,874 학습 시에 pretrained models이 중요하다는 것은 여러분도 잘 하실 것입니다. 497 00:50:15,874 --> 00:50:21,072 Tensorboard라는 것도 있습니다. 자세히 들어가진 않겠지만 498 00:50:21,072 --> 00:50:27,747 Tensorflow를 사용하게 되면 Training 하는 동안 Loss와 같은 것들을 plot할 수 있습니다. 499 00:50:27,747 --> 00:50:32,760 Tensorflow는 분산처리도 지원하기 때문에 서로 다른 머신을 이용해 graph를 쪼개서 실행시킬 수도 있습니다. 500 00:50:32,760 --> 00:50:37,613 Google 외에 이 기능을 잘 활용하는 사람들을 본 적은 없지만 501 00:50:37,613 --> 00:50:44,193 만일 여러분이 분산처리를 계획하고 있다면 Tensorflow가 유일한 선택지가 될 것입니다. 502 00:50:44,193 --> 00:50:51,533 그리고 추가로, Tensorflow는 Montreal에서 만든 Theano 라는 프레임워크의 영감을 받았습니다. 503 00:50:51,533 --> 00:50:55,933 여기서 더 자세히는 설명히 드리지 않겠지만 여러분이 집에가서 이 슬라이드를 다시 한번 살펴 보시게 되면 504 00:50:55,933 --> 00:50:59,979 Theano로 작성된 코드가 TensorFlow와 아주 유사하다는 것을 아실 수 있을 것입니다. 505 00:50:59,979 --> 00:51:03,512 변수를 정의하고 forward pass를 수행하고 그레디언트를 계산합니다. 506 00:51:03,512 --> 00:51:08,034 그리고 함수 몇개를 컴파일하고 네트워크를 계속 돌리는 것이죠 507 00:51:08,034 --> 00:51:10,290 이 모든 과정이 TensorFlow와 아주 유사합니다. 508 00:51:10,290 --> 00:51:16,671 아직 갈 길이 멀군요 PyTorch로 넘어갔다가 질문을 받고 수업을 끝내도록 하겠습니다. 509 00:51:16,671 --> 00:51:26,397 Facebook에서 나온 PyTorch는 TensorFlow와는 다릅니다. 세 가지 추상화 레벨을 정의해 놓았죠 510 00:51:26,397 --> 00:51:30,619 PyTorch에는 tensor object가 있습니다. Numpy array와 유사하죠 511 00:51:30,619 --> 00:51:36,770 Tensor는 명령형(imperative) 배열이고 GPU에서 수행될 수 있습니다. 512 00:51:36,770 --> 00:51:44,093 그리고 variable객체는 그래프의 노드라고 할 수 있습니다. 그래프를 구성하고 그레디언트 등을 계산할 수 있습니다. 513 00:51:44,093 --> 00:51:50,766 그리고 module 객체를 이용해서 Neural network를 구성할 수 있습니다. 514 00:51:50,766 --> 00:52:01,457 PyTorch와 TensorFlow를 간단히 비교해 보자면 PyTorch 의 Tensor는 Tensorflow의 Numpy array와 같은 역할이죠 515 00:52:01,457 --> 00:52:08,803 PyTorch의 variable는 Tensorflow의 tensor, variavle 또는 placeholder와 같습니다. 516 00:52:08,803 --> 00:52:18,448 PyTorch의 module은 tf.slim이나 tf.layers 혹은 그 밖에 다양한 higher level 프레임워크와 유사합니다. 517 00:52:18,448 --> 00:52:24,072 PyTorch에 대해 한가지 명심해야 할 점은 PyTorch가 고수준의 추상화를 이미 내장하고 있습니다. 518 00:52:24,072 --> 00:52:29,780 바로 modules 객체가 그것인데, 때문에 Tensorflow처럼 어떤 모듈을 선택할 지 고민할 필요가 없습니다. 519 00:52:29,780 --> 00:52:36,642 그런 고민 없이 module 객체만 사용하면 됩니다. 520 00:52:37,777 --> 00:52:41,944 앞서 말씀 드렸듯 PyTorch의 tensor는 Numpy array와 유사합니다. 521 00:52:43,660 --> 00:52:47,787 여기 오른쪽 예제를 보시면 PyTorch tensor로 구성한 2-layer 네트워크를 보실 수 있습니다. 522 00:52:47,787 --> 00:52:53,910 하지만 실제 Numpy array를 사용하지는 않고 PyTorch tensor를 이용합니다. 523 00:52:53,910 --> 00:53:01,245 이 코드는 Pytorch tensor로 짜여 졌음에도 불구하고 여러분이 첫 과제에서 했던 Numpy 코드와 아주 유사합니다. 524 00:53:01,245 --> 00:53:07,127 위에서 Random data를 선언하고 forward pass 를 계산하는 연산을 이용합니다. 525 00:53:07,127 --> 00:53:10,165 그리고 여기에서는 backward pass도 직접 구현했습니다. 526 00:53:10,165 --> 00:53:15,980 네트워크의 backprop을 구현하는 것을 과제 1에서 여러분들은 다 해 보셨을 것입니다. 527 00:53:15,980 --> 00:53:22,672 그레디언트를 계산하고 Learning rate를 이용해서 가중치를 직접(manual) 계산했습니다. 528 00:53:22,672 --> 00:53:27,785 다만 PyTorch tensor와 Numpy의 가장 큰 차이점이 있다면 PyTorch tensor는 GPU에서도 돌아간다는 것입니다. 529 00:53:27,785 --> 00:53:33,034 이 코드를 GPU에서 실행시키려면 data type만 조금 변경해주면 됩니다. 530 00:53:33,034 --> 00:53:42,816 FloatTensor 대신에 cuda.FloatTensor을 사용하면 모든 것들이 마법처럼 GPU에서 실행됩니다. 531 00:53:43,709 --> 00:53:47,637 PyTorch tensor를 쉽게 생각해서 Numpy + GPU 라고 보시면 되겠습니다. 532 00:53:47,637 --> 00:53:50,818 딥러닝과 직접적으로 맞닿아 있는 것은 아닙니다. 533 00:53:52,638 --> 00:53:55,278 다음은 variable에 대해 알아보겠습니다. 534 00:53:55,278 --> 00:54:03,460 variables은 computational graphs를 만들고 이를 통해 그레디언트를 자동으로 계산하는 등의 목적으로 이용합니다. 535 00:54:03,460 --> 00:54:12,744 X는 variable이고 x.data는 tensor 입니다. x.grad도 variable인데 Loss에 대한 그레디언트를 담고 있습니다. 536 00:54:14,007 --> 00:54:17,246 따라서 x.grad.data가 실제 tensor이고 이 안에 그레디언트가 담겨 있습니다. 537 00:54:18,972 --> 00:54:22,387 그리고 PyTorch의 tensors 와 variables는 같은 API를 공유합니다. 538 00:54:22,387 --> 00:54:28,457 PyTorch tensors로 동작하는 모든 코드는 variables로도 만들 수 있습니다. 539 00:54:28,457 --> 00:54:34,459 그렇게 되면 imperative한 연산자들이 수행되는 것이 아니라 Computational graph를 만드는 것이 됩니다. 540 00:54:35,943 --> 00:54:47,461 따라서 여기 보시면 variables를 선언할 때 해당 variables에 대한 그레디언트를 계산할 것인지를 지정해 줄 수 있습니다. 541 00:54:47,461 --> 00:54:54,073 그리고 forward pass의 경우를 보면 tensor를 사용했을 때와 완전히 같은 코드입니다. 왜냐하면 이 둘은 같은 API이기 떄문이죠 542 00:54:54,073 --> 00:54:59,683 예측값(y_pred)와 손실(loss)를 계산할 때 이런 식으로 imperative한 방법을 사용할 수 있습니다. 543 00:54:59,683 --> 00:55:05,251 그리고 loss.backwards 를 호출하게 되면 그레디언트가 알아서 반환이 됩니다. 544 00:55:05,251 --> 00:55:11,528 그리고 w1.grad.data의 값을 이용해서 가중치 업데이트를 할 수 있습니다. 545 00:55:11,528 --> 00:55:18,137 그레디언트가 자동으로 계산된다는 것 이외에는 Numpy와 아주 유사합니다. 546 00:55:18,137 --> 00:55:23,353 그렇다면 Tensorflow와 PyTorch의 차이는 무엇일까요? 547 00:55:23,353 --> 00:55:27,132 TensorFlow에 경우에는 그래프를 "명시적으로" 구성한 다음에 그 그래프를 돌렸습니다. 548 00:55:27,132 --> 00:55:32,152 PyTorch의 경우에는 forward pass 할 때 마다 매번 그래프를 다시 구성합니다. 549 00:55:32,152 --> 00:55:37,058 그때문에 코드가 더 깔끔해 보이기도 합니다. 이에 대해 조금 더 알아보도록 하겠습니다. 550 00:55:37,058 --> 00:55:40,630 PyTorch에서는 여러분만의 새로운 "자동으로 그레디언트를 계산하는(autograd)" 함수를 정의할 수 있습니다. 551 00:55:40,630 --> 00:55:42,933 tensors의 형태로 forward와 backward를 정의해주면 됩니다. 552 00:55:42,933 --> 00:55:48,303 이는 여러분이 과제2에서 수행했던 모듈 레이어와 비슷하게 생겼습니다. 553 00:55:48,303 --> 00:55:54,433 여러분이 tensor operations을 이용해서 forward/ backward 만 구현하면 그래프에 넣을 수 있습니다. 554 00:55:54,433 --> 00:56:00,654 여기 예제에서 ReLU를 직접 한번 구현해 봤습니다. 이런 식으로 직접 구현하게 되면 555 00:56:00,654 --> 00:56:05,214 이 ReLU를 computational graph에 적용할 수 있습니다. 556 00:56:05,214 --> 00:56:09,097 하지만 대부분의 경우에 이렇게 직접 autograd를 구현할 필요는 없을 것입니다. 557 00:56:09,097 --> 00:56:14,246 대부분의 경우에 여러분이 필요한 연산들은 이미 구현이 되어있기 때문입니다. 558 00:56:14,246 --> 00:56:23,349 Tensorflow의 경우에는 Keras나 TF.Learn과 같은 라이브러리가 higher level API 를 제공해 주었습니다. 559 00:56:23,349 --> 00:56:30,948 PyTorch에서는 그 역할을 "nn package"가 담당합니다. high level wrappers를 제공해 줍니다. 560 00:56:31,882 --> 00:56:37,772 다만 TensorFlow에는 종류가 다양했죠 하지만 PyTorch에서는 단 하나만 있는데 아주 잘 동작하고 쓸만합니다. 561 00:56:37,772 --> 00:56:44,436 Linear/ReLU layer를 model sequence에 추가하는 부분은 Keras와 유사하게 생겼습니다. 562 00:56:44,436 --> 00:56:49,816 그리고 바로 밑에는 nn package에서 제공하는 손실함수를 정의해 줍니다. 여기에서는 mean squared error loss 이군요 563 00:56:49,816 --> 00:56:55,214 그리고 매 반복마다 forward pass를 수행하여 prediction 결과를 얻습니다.(첫 번째 라인) 564 00:56:55,214 --> 00:56:59,054 그리고 손실함수를 실행하여 Loss도 구해줍니다. (두 번째 라인) 565 00:56:59,054 --> 00:57:04,021 그리고 loss.backward를 호출하면 매 반복시 마다 그래디언트가 저절로 계산이 됩니다. 566 00:57:04,021 --> 00:57:07,273 그리고 모델 업데이트를 위해 gradient descent step을 명시적으로 수행시켜 줍니다. 567 00:57:07,273 --> 00:57:12,749 앞서 말씀드렸듯 PyTorch는 forward pass 할 때 마다 매번 새로운 computational graph를 만들어 주는 것입니다. 568 00:57:12,749 --> 00:57:17,017 TensorFlow에서 처럼 PyTorch도 optimizer operations를 제공합니다. 569 00:57:17,017 --> 00:57:23,000 가중치 업데이트 부분을 추상화시켜서 Adam 과 같은 알고리즘을 더 쉽게 쓸 수 있습니다. 570 00:57:23,000 --> 00:57:28,771 이런 식으로 optimizer 객체를 구성해 놓는 것은 모델에게 파라미터를 optimize하고 싶다고 말해 주는 것입니다. 571 00:57:28,771 --> 00:57:31,115 learning rate같은 하이퍼 파라미터도 정해줘야 합니다. 572 00:57:31,115 --> 00:57:39,810 그리고 그레디언트를 계산하고 난 후에 optimizer.step를 호출하게 되면 모델 파라미터가 업데이트 됩니다. 573 00:57:39,810 --> 00:57:44,714 PyTorch를 사용할 때 여러분들이 아주 흔하게 접하게 될 것은 바로 여러분만의 nn modules을 정의하는 것입니다. 574 00:57:44,714 --> 00:57:51,801 여러분의 전체 네트워크 모델이 정의되어 있는 class를 nn module class로 작성해야만 합니다. 575 00:57:51,801 --> 00:58:01,043 module은 일종의 네트워크 레이어라고 보시면 됩니다. 다른 module이 포함될 수도 있고 학습가능한 가중치도 포함이 될 수 있죠 576 00:58:01,043 --> 00:58:07,051 여기 2-Layer Network 예제를 다시한번 보겠습니다. nn module class로 작성이 되어있는 예제입니다. 577 00:58:07,051 --> 00:58:11,672 이 클래스의 생성자를 보면 linear1과 linear2가 선언이 되어 있습니다. 578 00:58:11,672 --> 00:58:17,257 이 두 개의 module objects를 여러분의 클래스 안에 저장하고 있는 것입니다. 579 00:58:17,257 --> 00:58:26,466 그리고 forward pass에서는 네트워크 출력을 계산하기 위해 앞서 정의한 모듈도 사용할 수 있고 다양한 autograd도 사용할 수 있습니다. 580 00:58:26,466 --> 00:58:31,594 forward함수 내부를 한번 들여다보면 입력 x는 variable입니다. 581 00:58:31,594 --> 00:58:35,817 입력 x가 첫 번째 레이어인 self.linear 1을 통과합니다. 582 00:58:35,817 --> 00:58:38,129 그리고 autograd op을 사용해서 relu를 계산합니다. 583 00:58:38,129 --> 00:58:42,233 그리고 그 출력 값이 linear2를 통과해서 다시 값을 출력합니다. 584 00:58:42,233 --> 00:58:46,633 그리고 이 밑의 나머지 코드는 이전과 동일합니다. 585 00:58:46,633 --> 00:58:54,676 optimizer를 구성하고 반복문을 돌면서 데이터를 넣어주고 backwards로 그래디언트를 구하고 step으로 업데이트하죠 586 00:58:54,676 --> 00:59:01,817 이 예제가 PyTorch로 학습을 하는 경우 가장 일반적인 패턴입니다. 587 00:59:01,817 --> 00:59:11,166 우선 모델을 구성하는 클래스를 정의하고 반복문을 돌면서 이 모델을 업데이트 하는 것입니다. 588 00:59:11,166 --> 00:59:18,873 PyTorch에는 dataloader가 아주 유용합니다. dataloader는 여러분을 위해 minibatches를 관리하죠 589 00:59:18,873 --> 00:59:27,273 학습 도중 Disk에서 minibatches를 가져오는 일련의 작업들을 multi-threading를 통해 알아서 관리해 줍니다. 590 00:59:27,273 --> 00:59:33,221 여기 처럼 dataloader는 dataset를 wrapping하는 일종의 추상화 객체를 제공해 줍니다. 591 00:59:33,221 --> 00:59:40,208 실제로 여러분의 데이터를 이용하고자 할 때 데이터를 어떤 방식으로 읽을 것이지를 명시하는 dataset class만 작성해 준다면 592 00:59:40,208 --> 00:59:44,458 이 class를 dataloader로 wrapping 시켜서 학습을 시킬 수 있을 것입니다. 593 00:59:44,458 --> 00:59:52,233 여기 보시면 dataloader 객체를 순회하면서 매 반복시 마다 데이터의 minibatch를 적절하게 반환시켜 줍니다. 594 00:59:52,233 --> 00:59:58,409 그리고 내부적으로 data shuffling이나 multithreaded dataloading와 같은 것들을 알아서 관리해 주게 됩니다. 595 00:59:58,409 --> 01:00:04,161 지금 보시는 예제가 여러분이 PyTorch 를 사용할 때 가장 많이 보게 될 코드패턴이라고 보시면 되겠습니다. 596 01:00:05,583 --> 01:00:07,587 PyTorch는 pretrained models를 제공합니다. 597 01:00:07,587 --> 01:00:11,521 제가 본것 중에서 PyTorch의 pretrained model이 가장 간드러졌습니다. 598 01:00:11,521 --> 01:00:14,268 torchvision.models.alexnet(pretained=true) 라고만 쓰면 끝입니다. 599 01:00:14,268 --> 01:00:18,759 이 코드는 여러분이 이 가중치를 처음 사용하는 경우라면 저절로 pretrained models을 다운로드하게 해줍니다. 600 01:00:18,759 --> 01:00:24,242 사용하기 아주 쉽습니다. 601 01:00:24,242 --> 01:00:27,094 PyTorch는 또한 Visdom라는 패키지를 제공합니다. 602 01:00:27,094 --> 01:00:33,600 Tensorboard와 유사하게 Loss에 대한 통계같은 것들을 시각화해주는 패키지입니다. 603 01:00:33,600 --> 01:00:38,569 아주 좋은 패키지이긴 하나 제가 직접 사용해볼 기회가 없어서 Visdom이 얼마나 유용한지 말씀드릴 수는 없을 것 같습니다. 604 01:00:38,569 --> 01:00:45,907 다만 Tensorboard와 Visdom의 차이가 있다면 Tensorboard 의 경우에 computational graph의 시각화를 제공합니다. 605 01:00:45,907 --> 01:00:50,989 이는 아주 유용한 debugging strategy가 될 수 있죠 하지만 Visdom는 이 기능을 제공하지 않습니다. 606 01:00:50,989 --> 01:00:54,761 하지만 제가 Visdom을 직접 써보지 않아서 활요에 대해 심도깊게 말씀드리기 어려울 것 같습니다. 607 01:00:56,350 --> 01:01:05,491 어쨋든, PyTorch는 제가 지난 수년간 사용했던 Torch 라는 프레임워크가 새롭게 진화된 프레임워크입니다. 608 01:01:05,491 --> 01:01:13,280 깊게 말씀드리진 않겠지만 PyTorch가 기존의 Lua Torch보다 훨씬 더 좋은 것은 사실입니다만 609 01:01:13,280 --> 01:01:18,100 사실 이 둘 모두 동일한 C code Backend를 가지고 있습니다. tensors를 계산하거나 GPU 연산을 하는 등에 있어서 말이죠 610 01:01:18,100 --> 01:01:23,369 여기 Torch 예제를 보시면 일부는 PyTorch와 유사하고 또 일부는 조금 다른 부분도 있습니다. 611 01:01:23,369 --> 01:01:25,957 집에 가서 한번 살펴보시기 바랍니다. 612 01:01:25,957 --> 01:01:33,011 Torch와 PyTorch의 주된 차이점을 한번 비교해 보자면 Torch는 Python이 아닌 Lua로 작성되어 있습니다. 613 01:01:33,011 --> 01:01:37,748 Lua를 배우는 것은 일부 사람들에게 있어서는 시간낭비 일 수도 있을 것입니다. 614 01:01:37,748 --> 01:01:40,009 Torch는 autograd도 제공하지 않습니다. 615 01:01:40,009 --> 01:01:44,324 하지만 Torch가 더 오래되었기 때문에 안정성이 조금 더 보장되고 버그도 적습니다. 그리고 Torch의 예제코드가 더 많죠 616 01:01:45,230 --> 01:01:47,214 이 둘의 연산 속도도 거의 비슷합니다. 617 01:01:47,214 --> 01:01:54,531 반면 PyTorch는 Python으로 작성할 수 있으며 autograd도 지원합니다. 복잡한 모델을 더 쉽게 다룰 수 있겠지요 618 01:01:54,531 --> 01:01:59,670 Lua Torch는 대부분의 경우 backporp 코드를 직접 작성해야 합니다. 아주 귀찮은 일입니다. 619 01:01:59,670 --> 01:02:06,051 하지만 PyTorch가 더 최신에 나왔기 때문에 예제 코드가 더 적습니다. 따라서 PyTorch를 사용하는 것이 좀 더 모험적인 일이 될 수 있습니다. 620 01:02:06,051 --> 01:02:17,765 하지만 저같은 경우에는 굳이 PyTorch를 두고 Torch를 써야만 하는 이유를 못느끼기 때문에 현재는 거의 PyTorch만 사용하고 있습니다. 621 01:02:18,606 --> 01:02:22,531 앞서 static graphs VS dynamic graphs 에 대해서 말씀드린 적이 있었죠 622 01:02:22,531 --> 01:02:26,291 이는 PyTorch와 TensorFlow의 주된 차이점 중 하나입니다. 623 01:02:26,291 --> 01:02:38,145 TF는 두 단계로 나뉩니다. 첫째는 그래프를 구성하는 단계입니다. 그리고 두번째는 이 그래프를 반복적으로 돌리는 단계입니다. 624 01:02:38,145 --> 01:02:42,403 이를 static computational graph라고 합니다. 그래프가 단 하나만 고정적으로 존재하기 때문이죠 625 01:02:42,403 --> 01:02:48,771 PyTorch는 완전히 다른 방식입니다. 매번 forward pass할 때 마다 새로운 그래프를 구성합니다. 626 01:02:48,771 --> 01:02:52,259 이를 dynamic computational graph라고 합니다. 627 01:02:52,259 --> 01:02:57,053 이런 단순한 feed forward neural network의 경우에는 별로 큰 차이점이 없어 보일 수도 있습니다. 628 01:02:57,053 --> 01:03:00,225 코드도 비슷하게 생겼고 동작도 아주 비슷하죠 629 01:03:00,225 --> 01:03:07,102 하지만 static과 dynamic간의 서로 다른 특징과 trade-off에 대해서 조금 더 말씀드리고자 합니다. 630 01:03:07,102 --> 01:03:15,286 static graphs의 관점에서 보면 사실 그래프를 한번 구성해 놓으면 학습시에 똑같은 그래프를 아주 많이 재사용하게 됩니다. 631 01:03:15,286 --> 01:03:19,571 static graphs에서는 그 그래프를 최적화시킬 기회가 주어질 수 있는 것입니다. 632 01:03:19,571 --> 01:03:26,809 일부 연산들을 합쳐버리고 재배열시키는 등으로 가장 효율적으로 연산을 하도록 최적화 시킬 수 있는 것이죠. 아주 효율적인 방법입니다. 633 01:03:26,809 --> 01:03:33,039 그래프 하나만 하지고 아주 여러번 사용할 것이기 때문에 처음의 최적화작업 자체가 조금 오래 걸릴순 있어도 634 01:03:33,039 --> 01:03:37,230 최적화된 그래프를 여러번 사용한다는 것을 고려해보면 최적화에 소요되는 시간은 아무것도 아닐 수 있습니다. 635 01:03:37,230 --> 01:03:44,085 조금 더 구체적인 예를 들어보자면 여기 Conv와 ReLU가 반복되는 모델이 있다고 해봅시다. 636 01:03:44,085 --> 01:03:54,530 이 그래프를 최적화 한다고 했을때 Conv와 ReLU를 합쳐버리는 것을 생각해 볼 수 있을 것입니다. 637 01:03:54,530 --> 01:04:03,445 어짜피 합쳐도 같은 연산을 하는 것이고 대신 코드는 더 효율적으로 실행될 수 있을 것입니다. 638 01:04:03,445 --> 01:04:10,419 저도 TensorFlow graph optimization가 실제로 어떤 방식으로 동작하는지 확실하게 알지는 못하지만 639 01:04:10,419 --> 01:04:20,131 하지만 확실한 것은 static graphs에서는 그래프를 최적화시킬 수 있는 여지가 주어진다는 것입니다. 640 01:04:20,131 --> 01:04:24,298 dynamic graphs 에서는 그래프 최적화를 다루기 어려울 것입니다. 641 01:04:25,504 --> 01:04:28,931 static vs dynamic의 또 한가지 차이점은 serialization에 관한 것입니다. 642 01:04:28,931 --> 01:04:34,026 static graph 를 사용하여 그래프를 구성한다고 해봅시다 643 01:04:34,026 --> 01:04:39,571 그래프를 한번 구성해 놓으면 메모리 내에 그 네트워크 구조를 가지고 있는 것입니다. 644 01:04:39,571 --> 01:04:42,428 그렇게 되면 그 자체를 Disk에 저장할 수 있을 것입니다. 645 01:04:42,428 --> 01:04:45,996 이렇게 되면 전체 네트워크 구조를 파일 형태로 저장할 수 있는 것입니다. 646 01:04:45,996 --> 01:04:55,450 그러고 나면, 원본 코드 없이도 그래프를 다시 불러올 수 있습니다. 이는 아주 좋은 특성입니다. 647 01:04:55,450 --> 01:05:00,424 가령 여러분은 네트워크를 Python으로 학습시키고 싶을 것입니다. Python이 쉽기 때문이죠 648 01:05:00,424 --> 01:05:07,759 그리고 이를 serialize하면, C++환경에서 아주 쉽게 동작시킬 수 있습니다. 그래프를 구성하는데 기존의 코드는 필요하지 않습니다. 649 01:05:07,759 --> 01:05:10,909 이는 static graphs의 아주 좋은 이점이라고 할 수 있습니다. 650 01:05:10,909 --> 01:05:15,793 반면에 dynamic graph의 경우에는 "그래프 구성" 과 "그래프 실행" 하는 과정이 얽혀 있기 때문에(interleaving) 651 01:05:15,793 --> 01:05:22,012 모델을 재사용하기 위해서는 항상 원본 코드가 필요합니다. 652 01:05:22,012 --> 01:05:29,163 하지만 dynamic graphs의 장점은 대다수의 경우에 코드가 훨씬 더 깔끔하고 작성하기 더 쉽습니다. 653 01:05:29,163 --> 01:05:38,624 가령 조건부 연산(if 문)을 해야하는 상황을 가정해 봅시다. 변수 z의 값에 따라서 y값이 달라지는 경우입니다. 654 01:05:39,723 --> 01:05:45,070 Z가 양수면 w1*x 연산을, 음수면 w2*x 연산을 수행해야 하죠 655 01:05:45,070 --> 01:05:47,981 조건부 연산을 사용해서 이 두 가지 연산을 오가야 하는 상황입니다. 656 01:05:47,981 --> 01:05:52,011 PyTorch에서는 dynamic graphs를 사용하므로 이 문제를 다루기 아주 수월합니다. 657 01:05:52,011 --> 01:06:00,795 Numpy와 아주 유사합니다. 이 문제를 다루데는 일반적인 Python if문을 사용하면 그만이죠 658 01:06:00,795 --> 01:06:05,563 dynamic graphs의 경우에는 매번 새로운 그래프를 구성하기 때문에 659 01:06:05,563 --> 01:06:10,864 매번 이 두 가지 선택지 중에 현재 forward pass에 적절한 하나를 선택해서 새로운 그래프를 만들어주면 그만입니다. 660 01:06:10,864 --> 01:06:14,337 새롭게 구성된 그래프로 backporb에도 아무 문제가 없겠죠 661 01:06:14,337 --> 01:06:15,941 코드를 아주 깔끔하게 짤 수 있습니다. 662 01:06:15,941 --> 01:06:23,201 TensorFlow에서는 조건부 연산을 넣기 조금 더 복잡합니다. 왜냐하면 우선 그래프를 하나 만들어 놔야 합니다. 663 01:06:23,201 --> 01:06:28,400 그래프 내에 조건부 연산을 명시적으로 정의하는 Control flow operator를 추가해야만 합니다. 664 01:06:28,400 --> 01:06:36,818 Tensorflow의 예제에서는 tf.cond라는 것을 보실 수 있습니다. Tensorflow버전의 if문 이라고 보시면 됩니다. 665 01:06:36,818 --> 01:06:40,741 Python의 if문을 쓰는 것 대신에 이런 식으로 그래프 내에 control flow자체를 넣어줘야 하는 것입니다. 666 01:06:40,741 --> 01:06:48,729 Tensorflow에서는 그래프가 단 한번만 만들어지기 때문에 가능한 모든 control flow를 미리 고려해서 667 01:06:48,729 --> 01:06:52,523 그래프 내에 한번에 넣어줘야만 합니다. 그래프를 실제로 실행시키기 전에 말이죠 668 01:06:52,523 --> 01:07:03,360 Control flow를 위해서는 간단한 python 문법으론 불가능하고 반드시 특수한 Tensor Flow 연산자가 필요하다는 것입니다. 669 01:07:03,360 --> 01:07:05,527 지금 예제의 경우에는 tf.cond이 되겠습니다. 670 01:07:06,713 --> 01:07:10,763 조건부 연산과 유사한 상황이 반복연산(loop) 에서도 발생합니다. 671 01:07:10,763 --> 01:07:19,839 가령 어떤 재귀적인 연산을 한다고 해봅시다. Y_t = (Y_t-1 + X) * W 같은 경우죠 672 01:07:19,839 --> 01:07:26,436 이런 연산을 수행할 때 우리의 데이터 sequence는 다양한 사이즈일 수 있습니다. 673 01:07:26,436 --> 01:07:33,371 데이터의 sequence 길이가 얼마인지 신경쓰지 않고 재귀연산을 할 수 있다면 좋겠죠 674 01:07:33,371 --> 01:07:39,489 PyTorch의 경우에 엄청나게 간단합니다. Python에서 제공하는 기본 for loop를 이용하면 끝입니다. 675 01:07:39,489 --> 01:07:47,095 적절한 Loop만 돌면 됩니다. 이를 통해 데이터 사이즈에 맞는 적절한 그래프를 손쉽게 만들 수 있습니다. 676 01:07:47,095 --> 01:07:51,694 그리고 이는 backprob에도 지장이 없습니다. 677 01:07:51,694 --> 01:07:55,782 하지만 TensorFlow에서는 이 과정이 아주 지저분합니다. 678 01:07:55,782 --> 01:08:06,364 Tensorflow에서는 그래프를 앞에서 미리 만들어줘야 하기 때문에 그래프에 명시적으로 loop를 넣어줘야만 합니다. 679 01:08:06,364 --> 01:08:13,517 여러분이 함수형 프로그래밍을 잊지 않고있길 바랍니다. 왜냐하면 TensorFlow에서 looping을 구현하려면 반드시 필요하기 때문이죠 680 01:08:13,517 --> 01:08:23,024 이 경우에 특정 재귀적인 관계를 정의하기 위해서는 tf.foldl연산을 사용해서 구현해볼 수 있습니다. 681 01:08:24,100 --> 01:08:28,734 TensorFlow에 대해 기본적으로 말씀드리고 싶었던 것은 TensorFlow를 사용하게 되면 여러분들은 반드시 682 01:08:28,734 --> 01:08:33,212 computational graphs의 "전체 흐름" 을 여러분들이 전부 다 구성해 놔야 한다는 것입니다. 683 01:08:33,212 --> 01:08:37,215 그래프에 필요한 모든 control flow연산 그리고 모든 데이터 구조 등을 전부다 구현해 놔야 합니다. 684 01:08:37,215 --> 01:08:44,216 따라서 Tensorflow에서는 Python의 명령어들을 같이 활용할 여지가 별로 없습니다. 685 01:08:44,216 --> 01:08:52,804 TF에서 필요한 control flow 연산들을 전부 다시 배워야만 합니다. TensorFlow에서 그것을 사용해야 한다면 말이죠. 686 01:08:52,804 --> 01:08:58,238 적어도 저의 경우에는 Tenforflow를 사용할 때 상당히 혼란스러웠습니다. 687 01:08:58,238 --> 01:09:06,722 반면에 PyTorch의 dynamic graphs를 사용하는 경우라면 다양한 Python 명령어들을 이용할 수 있습니다. 688 01:09:07,737 --> 01:09:21,579 사실 TensorFlow Fold라는 TF 라이브러리가 있습니다. TensorFlow에서 dynamic graphs를 작성하게 해줍니다. 689 01:09:22,416 --> 01:09:32,277 TensorFlow Fold가 dynamic graph를 만들어주는 것 처럼 보이지만 사실 static graph로 만든 트릭입니다. 690 01:09:32,277 --> 01:09:37,357 TF Fold는 이번주에 ICLR in France에서 발표된 아주 최신의 연구(논문)입니다. 691 01:09:37,358 --> 01:09:41,694 그래서 아직 TF-Fold를 써보지는 못했습니다. 692 01:09:41,694 --> 01:09:46,455 하지만 제 첫인상으로는 TF-Fold를 통해 TensorFlow로 dynamic graphs 역할을 더해줄 순 있으나 693 01:09:46,455 --> 01:09:51,952 아직까지는 PyTorch같은 네이티브 dynamic graphs 에 비하면 많이 어색해 보였습니다. 694 01:09:51,952 --> 01:09:57,257 그렇다면 어떤 상황에서 dynamic graph의 사용을 고려해 볼만 한 것일까요? 695 01:09:57,257 --> 01:10:00,257 그중 하나는 바로 recurrent networks 의 경우입니다. 696 01:10:01,177 --> 01:10:07,612 image captioning의 경우가 그렇습니다. 이는 다양한 길이의 sequences를 다루기 위해 RNN을 이용합니다. 697 01:10:07,612 --> 01:10:13,337 이 예시의 경우에는 우리가 만들어내고자 하는 caption이 바로 sequence입니다. 698 01:10:13,337 --> 01:10:15,636 이 caption이라는 sequence는 입력 데이터에 따라서 다양하게 변할 수 있습니다. 699 01:10:15,636 --> 01:10:21,694 이 문제의 경우에는 dynamism을 가지고 있습니다. sentence의 크기에 따라서 - 700 01:10:21,694 --> 01:10:25,716 computational graph가 더 커질 수도, 작아질 수도 있을 것입니다. 701 01:10:25,716 --> 01:10:29,920 Image caption은 dynamic graphs를 이용하는 가장 일반적인 예시 중 하나입니다. 702 01:10:29,920 --> 01:10:36,377 지난 학기에 CS224N를 수강하신 분들이라면 recursive networks에 대해서 들어본 적이 있으실 것입니다. 703 01:10:36,377 --> 01:10:47,337 가령 자연어 처리 분야에서 문장을 파싱하는 문제에서 트리를 파싱하기 위해 recursive한 네트워크가 필요할 수 있습니다. 704 01:10:47,337 --> 01:10:56,856 이런 문제의 경우 layer의 "sequence 구조"를 이용하기 보다는 "graph 나 tree 구조" 를 이용합니다. 705 01:10:56,856 --> 01:10:58,732 데이터에 따라 다양한 graph 또는 tree 구조를 가질 수 있습니다. 706 01:10:58,732 --> 01:11:05,714 따라서 그래프를 구성할 때 데이터의 구조를 반영하도록 구성하면 다양한 데이터에도 유동적인 네트워크를 만들 수 있을 것입니다. 707 01:11:05,714 --> 01:11:10,316 이런 네트워크를 구성하려면 Tensorflow에서는 정말 복잡하고 까다로울 수 있습니다. 708 01:11:10,316 --> 01:11:14,887 하지만 PyTorch를 사용하게 되면 기존의 Python control flow 만 사용하더라도 충분합니다. 709 01:11:16,574 --> 01:11:23,678 VQS를 다루는 Neuromodule라는 연구가 있습니다. 아주 멋진 아이디어입니다. 710 01:11:23,678 --> 01:11:31,737 "이미지"와 "질문"을 던지면 적절한 답을 하는 것이죠. 이 이미지에는 고양이와 강아지가 있군요 711 01:11:31,737 --> 01:11:43,594 "고양이의 색은?" 과 같은 질문을 던지면 이 문제를 풀기에 적합한 네트워크 를 구성합니다. 여기에선 "색" 과 "고양이" 를 찾게 되겠죠 712 01:11:43,594 --> 01:11:49,838 질문이 주어지면 그 질문에 답하기 위한 적절한 네트워크 (custom)를 구성하는 것입니다. 713 01:11:49,838 --> 01:11:55,094 "고양이가 강아지보다 많은지?" 와 같은 질문을 한다면 어떻까요? 714 01:11:55,094 --> 01:12:03,076 이 경우에도 이전과 같이 "고양이"와 "강아지" 를 찾는 네트워크가 들어갈 테지만 조금은 구성이 달라질 것입니다. 715 01:12:03,076 --> 01:12:07,716 여기에서 dynamism이 적용될 것입니다. 문제가 달라지면 computation graph도 다르게 구성되겠죠 716 01:12:07,716 --> 01:12:12,574 아직 연구가 진행 중인 분야이며 아직 까지는 main stream은 아닙니다. 717 01:12:12,574 --> 01:12:19,214 가장 중요하게 말씀드리고 싶은 것은 dynamic graph를 이용한다면 정말 많은 것들을 할 수 있다는 것입니다. 718 01:12:19,214 --> 01:12:23,471 아직은 Dynamic 그래프를 다루는 것이 쉽지 않기 때문에 관련 연구가 그렇게 많지는 않습니다. 719 01:12:23,471 --> 01:12:30,596 Dynamic computrational graphs를 사용한다면 아주 다양한 대단하고 창의적인 것들을 만들어 낼 수 있을 것이라 생각합니다. 720 01:12:30,596 --> 01:12:34,078 여러분들이 좋은 아이디어를 만들어 내신다면 제가 내년 이 강의해서 소개해 드리도록 하겠습니다. 721 01:12:34,078 --> 01:12:39,854 자 이제 Caffe에 대해서 간단하게 말씀드려보겠습니다. Berkeley에서 처음 만든 프레임워크입니다. 722 01:12:39,854 --> 01:12:48,815 Caffe는 다른 딥러닝프레임워크랑은 다소 다릅니다. 많은 경우에 코드를 작성하지 않아도 네트워크를 학습시킬 수 있죠 723 01:12:48,815 --> 01:12:53,214 기존에 빌드된 바이너리를 호출하고 Configuration 파일만 조금 손보면 724 01:12:53,214 --> 01:12:56,697 굳이 코드를 작성하지 않아도 데이터를 학습시킬 수 있습니다. 725 01:12:56,697 --> 01:13:03,054 우선 데이터를 HDF5 또는 LMDB포맷으로 변환시켜 줍니다. 726 01:13:03,054 --> 01:13:08,638 이는 Caffe에서 지원하는 스크립트를 이용하면 됩니다. 폴더안에 저장된 이미지를 적절한 포맷으로 변환시켜줍니다. 727 01:13:08,638 --> 01:13:19,934 Caffe에서는 그래프를 구성하려면 코드를 작성하는 대신에 prototxt라는 텍스트파일을 만들어야 합니다. 728 01:13:19,934 --> 01:13:30,875 여기 구조를 보시면 입력을 HDF5 파일로 받는 부분도 있고 내적을 하는 부분도 있습니다. 이런 형식으로 그래프를 구성합니다. 729 01:13:30,875 --> 01:13:35,956 이런 방식은 네트워크의 규모가 커지면 상당히 보기 안좋다는 단점이 있습니다. 730 01:13:35,956 --> 01:13:44,253 가령 152레이어의 ResNet 모델을 Caffe로 훈련시키려 하면 prototxt 파일은 거의 7000줄에 이르게 됩니다. 731 01:13:44,253 --> 01:13:51,817 그래서 사람들은 이것을 손으로 직접 작성하기보다는 prototxt 파일을 생성하는 Python 스크립트를 작성하곤 합니다. 732 01:13:51,817 --> 01:13:53,275 [웃음] 733 01:13:53,275 --> 01:13:58,974 Caffe에서는 그래프를 하나하나 전부 다 기술해 줘야 합니다. 그닥 좋은 방법은 아닌 것 같아 보이지만 말이죠 734 01:13:58,974 --> 01:14:07,497 그리고 Caffe는 optimizer나 solver를 정의하기 위한 또 다른 prototxt 파일을 작성해야 합니다. 735 01:14:07,497 --> 01:14:11,036 그 곳에 learning rate와 optimiation algorithm 같은 것들을 정의해 줍니다. 736 01:14:11,036 --> 01:14:17,278 이런 식의 작업을 모두 마치고 Caffe의 바이너리만 실행시키면 학습이 자동으로 수행됩니다. 737 01:14:17,278 --> 01:14:21,294 Caffe에도 아주 다양한 pretrained model을 제공하는 model zoo가있습니다. 아주 유용하죠 738 01:14:21,294 --> 01:14:25,438 그리고 Caffe는 Python 인터페이스를 지원하긴 하지만 문서화가 잘 되어있는 편은 아닙니다. 739 01:14:25,438 --> 01:14:31,455 python 인터페이스와 관련된 소스코드도 있긴 하지만 상당히 불편합니다. 그렇긴 해도 동작은 합니다. 740 01:14:31,455 --> 01:14:40,174 Caffe의 경우에는 feed forward 모델에 적합한 편입니다. 그리고 production의 측면에 적합하다고 볼 수 있습니다. 741 01:14:40,174 --> 01:14:42,796 왜냐하면 Python에 의존적이지 않기 때문이죠 742 01:14:42,796 --> 01:14:47,358 하지만 오늘날 Caffe는 research의 목적으로는 잘 사용하지 않습니다. 743 01:14:47,358 --> 01:14:51,417 하지만 Caffe는 industry에서 production 개발을 위해서 여전히 많이 사용합니다. 744 01:14:51,417 --> 01:14:54,410 한 두개의 슬라이드로 Caffe2를 간단히 설명드리겠습니다. 745 01:14:54,410 --> 01:14:58,596 Caffe 2를 Caffe의 다음 버전입니다. Facebook에서 만들었죠 746 01:14:58,596 --> 01:15:02,432 지난 주에 배포된 아주 따끈한 신상이죠 (2017년 4월 27일 기준) 747 01:15:02,432 --> 01:15:04,436 [웃음] 748 01:15:04,436 --> 01:15:09,314 Caffe2를 유심히 살펴볼 새가 없었습니다. 749 01:15:09,314 --> 01:15:12,318 Caffe2도 TensorFlow처럼 Static graph를 사용합니다. 750 01:15:12,318 --> 01:15:17,817 Caffe처럼 코어는 C++로 작성되어있고 Python 인터페이스도 제공합니다. 751 01:15:17,817 --> 01:15:21,518 Caffe1과 다른점이 있다면 더이상 prototxt 파일을 만들기 위해 Python 스크립트를 작성할 필요가 없다는 것입니다. 752 01:15:21,518 --> 01:15:29,657 Tensorflow 스럽게 Python으로 그래프를 작성할 수 있습니다. 753 01:15:29,657 --> 01:15:34,596 그리고 이 그래프를 prototxt 파일로 변환시켜 줄 수 있습니다. 754 01:15:34,596 --> 01:15:38,676 모델을 한번 훈련시켜 놓게 되면 앞서 Static 그래프의 이점에 대해서 말씀드렸듯이 755 01:15:38,676 --> 01:15:43,534 기존의 소스코드 없이도 학습시킨 모델을 쓸 수 있는 것입니다. 756 01:15:43,534 --> 01:15:49,417 한 가지 흥미로운 점은 Google의 경우에는 주력 딥러닝 프레임워크가 TensorFlow 하나 뿐입니다. 하지만 - 757 01:15:49,417 --> 01:15:53,761 Facebook의 경우에는 두 개죠 PyTorch와 Caffe2 입니다. 758 01:15:54,596 --> 01:15:57,252 이 차이는 서로 다른 철학에서 비롯됩니다. 759 01:15:57,252 --> 01:16:02,847 Google의 경우에는 딥러닝이 필요한 모든 곳에서 동작하는 프레임워크를 만들고싶어 합니다. 760 01:16:02,847 --> 01:16:07,852 그래서 통합된 프레임워크 하나를 만들고 그곳에 올인하는 것이죠 761 01:16:07,852 --> 01:16:13,772 TF하나로 distributed systems, production, deployment, mobile, research를 다 커버하도록 말이죠 762 01:16:13,772 --> 01:16:15,706 이 모든 것을 다루는데 프레임워크 하나면 충분합니다. 763 01:16:15,706 --> 01:16:18,151 하지만 Facebook의 생각은 조금 다릅니다. 764 01:16:18,151 --> 01:16:26,071 PyTorch는 연구(research)에 특화되어 있습니다. 연구 사이클을 상당히 단축하도록 만들어졌습니다. 765 01:16:26,071 --> 01:16:32,951 연구에는 PyTorch가 쉽지만 제품(production)개발을 위한 지원이 그닥 많지 않습니다. 766 01:16:32,951 --> 01:16:37,710 대신 Caffe2가 그 역할을 담당하는 것입니다. 767 01:16:39,567 --> 01:16:47,350 그렇다면 어떤 프레임워크를 어떻게 사용해야할지에 대한 제 주관적인 의견을 말씀드리자면 768 01:16:47,350 --> 01:16:53,510 Tensorflow 같은 경우에는 여러분이 어떤 프로젝트를 하던 상당히 괜찮은 선택입니다. 769 01:16:53,510 --> 01:16:58,849 Tensorflow는어떤 환경에서든 잘 동작하는 만능 프레임워크이기 때문입니다. 770 01:16:58,849 --> 01:17:05,207 하지만 여러 higher level wrapper를 섞어 써야 하거나 Dynamic graph가 필요한 경우 곤란해 질 수도 있습니다. 771 01:17:05,207 --> 01:17:13,190 Tensorflow의 Dynamic 그래프 코드가 조금 지저분하기 떄문이죠 물론 제 개인적인 의견이고, 사실 큰 문제도 아닙니다. 772 01:17:13,190 --> 01:17:15,809 저는 개인적으로 PyTorch가 research에는 아주 유용하다고 생각합니다. 773 01:17:15,809 --> 01:17:21,233 연구목적으로만 사용한다면 PyTorch가 최고의 선택이 될 수 있습니다. 774 01:17:21,233 --> 01:17:25,649 하지만 나온지 얼마 안되서 커뮤니티 규모가 작고 찾을만한 코드도 적습니다. 모험을 해야할 수도 있습니다. 775 01:17:25,649 --> 01:17:29,969 이미 사람들이 잘 다져놓은 길을 가고 싶다면 Tensorflow가 더 좋은 선택일 수 있습니다. 776 01:17:29,969 --> 01:17:34,710 만약 여러분이 제품을 배포하려는 목적이라면 Caffe, Caffe2 또는 Tensorflow를 선택해야 합니다. 777 01:17:34,710 --> 01:17:41,270 또한 특히 모바일 디바이스라면 Tensorflow와 Caffe2가 지원하는 것으로 알고 있습니다. 778 01:17:41,270 --> 01:17:47,393 불행하게도 무조건 이 프레임워크가 최고다 라는 것은 없습니다. 선택은 여러분의 문제의 성격에 달려있는 것입니다. 779 01:17:47,393 --> 01:17:52,045 앞서 말씀드린 내용들을 기준들은 제가 일반적으로 조언해 드리는 내용입니다. 780 01:17:53,169 --> 01:17:55,691 다음 강의에서는 다양한 CNN 아키텍쳐에 대해서 배워보도록 하겠습니다.